Skip to content

fix(version)!: sync every committed Cargo.lock that records a bumped crate - #674

Merged
goosewobbler merged 20 commits into
mainfrom
fix/650-cargo-lock-sync
Oct 23, 2026
Merged

goosewobbler merged 20 commits into
mainfrom
fix/650-cargo-lock-sync

Conversation

@goosewobbler

@goosewobbler goosewobbler commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Description

Releases now update every committed Cargo.lock that records a bumped crate, so no follow-up sync PR is needed. It needs no Rust toolchain or network, and a lock that can't be updated fails the run instead of warning.

Checked against webdriverio/desktop-mobile#748, the last manual sync PR: replaying its bumps changes the same four locks, byte for byte.

Every bump path syncs. Every version-step Cargo.toml write goes through updateCargoVersion, which now records the bump; VersionEngine.run syncs the locks once at the end. Groups, the per-package processor and updateCargoFiles are all covered, and a new bump path can't skip it. Every edit is worked out before any lock is written.

No cargo. The lock text is edited the way cargo would edit it: the entry's version line, any "<name> <old version>" references, and cargo's ordering when the lock also holds another version of the crate. Byte-identical to cargo for v3 and v4 locks.

Which locks. Every git-tracked Cargo.lock, plus each crate's own lock (at its workspace root, by cargo's rule) unless git ignores it. Crate names repeat across workspaces (every tauri init app is app, and 0.1.0 is cargo's default version), so another workspace's lock follows a bump only if that workspace reaches the crate's directory through path dependencies, as cargo resolves them: target-specific, workspace = true, and dev-dependencies of its own members. [patch] isn't followed yet (#706): cargo drops a patch the bumped version no longer satisfies.

Failures. An ambiguous lock (several path entries with the name, or another source holding the crate at the old or new version), or one in a format newer than v4, throws CARGO_LOCK_SYNC_ERROR, exit family VERSION_ERROR, in dry runs too. docs/rust.md covers each case.

Where the edits land.

  • Standing PR: the version step writes them and git add -A stages them.
  • releasekit version: writes them.
  • Direct release: flushed with the manifests; publish's prepare stage re-syncs and stages every lock that lists a bumped crate.
  • Publish's own sync only warns: after a standing-PR merge, a lock fix-up mustn't strand a release that's already on the base branch.

Breaking changes

  • Removed @releasekit/version's syncCargoLockfile export; only the integration test used it.
  • findCargoLockfile follows cargo's workspace-root rule, including exclude and package.workspace pointers, and no longer returns an ancestor lock that isn't the root.
  • New core exports: planCargoLockUpdates and its types. @releasekit/git gains lsFiles and isIgnored.
  • createSync/Single/AsyncStrategy called directly, without VersionEngine, no longer sync locks. No in-repo caller does that.

CARGO_LOCK_SYNC_ERROR sits next to where #616 expands CODE_TO_EXIT; whichever lands second keeps both.

Related Issue(s)

Closes #650. Follow-up to #496.

Type of Change

  • feat — New feature
  • fix — Bug fix
  • docs — Documentation change
  • refactor — Code refactoring
  • test — Adding or updating tests
  • chore — Maintenance, dependencies, tooling
  • Breaking change — Requires major version bump

Scope

  • @releasekit/version
  • @releasekit/notes
  • @releasekit/publish
  • @releasekit/config
  • @releasekit/core
  • CI / workflows
  • Root / monorepo

Checklist

  • Lint passes (pnpm lint)
  • Tests pass (pnpm test)
  • Typecheck passes (pnpm typecheck)
  • Documentation updated (if applicable)
  • Breaking changes noted in description

Testing Notes

  • test/integration/cargo-lock-sync.spec.ts runs the built CLI with no cargo and an empty CARGO_HOME, then checks the result with real cargo. Every lock passes cargo metadata --locked unchanged, for groups, the per-package processor and updateCargoFiles; the edit matches cargo update --workspace byte for byte, reordering included; a same-named app's lock is left alone. CI's Integration Tests job installs a Rust toolchain for it.
  • Unit tests drive every bump path through VersionEngine.run in a real git work tree, and cover the lock edit, the workspace-root rule and the planner in core.
  • Checked by hand against real cargo: prerelease crossings, and the 22 workspace layouts cargo accepts.
  • Lint, typecheck and every unit and integration suite pass.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge with the documented limitation for patch-only dependencies.

Summary

The PR updates related Cargo.lock files without running Cargo and fails the version step when a selected lock cannot be edited safely.

  • Cargo locks follow path-linked crates and receive Cargo-style version edits.
  • Version runs sync Cargo locks for every crate they bump.
  • Direct releases sync and stage every lock that records a bumped crate.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Write Cargo.toml versions] --> B[Record bumped crates]
  B --> C[Find related Cargo.lock files]
  C --> D[Plan all lock edits]
  D --> E{Can selected locks be edited?}
  E -->|Yes| F[Write locks or record dry-run edits]
  E -->|No| G[Fail the version step]
Loading

Reviews (5) · Last reviewed commit: "fix(core): stop following [patch] when s..." · Reviewed by Greptile

The #496 lock sync only ran from updateCargoFiles, so crates bumped
through version.groups (grouped and ungrouped) or the per-package
processor left their own entry in the committed Cargo.lock at the old
version. The sync now runs inside updateCargoVersion, the one function
every version-step Cargo.toml write goes through.

The sync also stops shelling out to `cargo update --workspace --offline`,
which exits 101 on a runner with an empty CARGO_HOME (no cached crates.io
index) and needs a Rust toolchain the release job may not have. Core's
new syncCargoLockVersion edits the lock text the way cargo does for a
path package's version change: the entry's version line plus any
"name old-version" dependency references. Registry and git entries are
never touched, and a lock where the edit would change how cargo tells
same-named packages apart is left alone with a warning. Publish's
prepare-stage sync uses the same helper.

Closes #650

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Standing release PR: #614 · v0.42.0 queued (4 packages) · open 82d 18h 14m · ✅ ready to merge

Release Preview — v0.41.2 → v0.41.3

Note: Labels on this PR are advisory in standing-pr mode. Bumps come from conventional commits in the standing PR; override by editing labels on the standing PR itself. Add release:immediate to bypass the standing PR and release this PR directly.

These changes will be added to the release PR (#614) when merged:

Changelog

monorepo 0.41.2 → 0.41.3

Changed

  • add a Live Tests job, starting with notes against Ollama Cloud (PR #697 · closes #589)
  • run tests when packages/forge or packages/git change (PR #705 · closes #704)
  • cut isOllamaUnreachable's tests to the three that pin its rule (notes) (PR #702)
  • generate release notes with the OLLAMA_MODEL repo variable's model (PR #703)
  • make the LLM e2e infra-tolerant and close a false green (notes) (PR #618)
  • cut Cargo.lock sync comments to the non-obvious
  • pin whole-component exclude matching; note the unfollowed workspace pointer (core)
  • note the any-version case of the same-name limitation (rust)
  • shorten the same-name limitation (rust)
  • narrow the same-name claim, note the residual case and exit codes (rust)
  • keep the exit-code table to families; shorten the lock-sync bullet
  • document CARGO_LOCK_SYNC_ERROR and how lock entries are matched
  • add braces to every single-statement body

Fixed

  • stop following [patch] when syncing Cargo.lock (core)
  • refuse to edit a Cargo.lock format newer than v4 (core)
  • follow package.workspace pointers, and only the root's [patch] (core)
  • sync another workspace's Cargo.lock only if it reaches the crate by path (core)
  • push the current branch when git.branch is unset (publish) (PR #696 · closes #688)
  • find a crate's Cargo.lock by cargo's workspace-root rule (core)
  • treat a lock beside a crate's manifest as its own (version)
  • sync a lock entry only for the crate it records, never an ignored lock (version)
  • keep cargo's order when a lock bump passes another version (core)
  • sync every committed Cargo.lock that records a bumped crate (version)
  • sync Cargo.lock on every Cargo.toml bump path (version) (#650)

All packages → 0.41.3 (sync versioning)

  • @releasekit/version
  • @releasekit/notes
  • @releasekit/publish
  • @releasekit/release

After merge — predicted release

No version escalation — this PR's changes will be included in the queued release without affecting the projected versions.

Package Standing PR This PR After merge
monorepo 0.42.0 0.41.3 0.42.0

Updated automatically by ReleaseKit

Comment thread packages/core/src/cargo.ts Outdated
…rate

#650 now asks that no committed Cargo.lock needs a follow-up sync PR after
a release.

- Locks that use a crate through a path dependency are synced too: for
  each bumped crate, every Cargo.lock tracked by git with a sourceless
  entry of that name is updated, leaving registry and git entries of the
  name alone. The crate's own lock is still synced even when untracked.
  The tracked locks are listed once per run through the git seam (new
  Git.lsFiles).
- updateCargoVersion records each bump and VersionEngine.run syncs the
  locks once the strategy is done, so every strategy is covered, a lock
  shared by several crates is rewritten once, and every edit is worked
  out before the first write.
- A lock that can't be synced fails the run with CargoLockSyncError
  (CARGO_LOCK_SYNC_ERROR, VERSION_ERROR exit family) instead of warning.
  A dry run works out the same edits, so it fails too, and records them
  as pending writes that release's flush writes with the manifests.
- Publish's prepare stage syncs and stages every such lock, so the
  direct-commit flow commits fixture locks as well. It stays non-fatal:
  in the standing-PR flow it runs after the release PR has merged.
- The real-cargo integration test runs `releasekit version` with no
  cargo on PATH and an empty CARGO_HOME for version.groups, the
  per-package processor and updateCargoFiles, and checks that
  `cargo metadata --locked` accepts every such lock unchanged.

The syncCargoLockfile export of @releasekit/version (added for the
integration test in #498) is removed: the sync is now per run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
Cargo orders same-name [[package]] entries by semver and a package's
dependency references by the version as a plain string, so
"dupe 10.0.0" sorts before "dupe 2.0.0" (checked with cargo 1.97).
Rewriting a path crate's version in place left both in their old order
whenever the new version passed a same-named registry or git entry, and
the next cargo run would reorder the lock.

setCargoLockVersion now moves the edited entry, and the run of
references around each edited reference, back to cargo's order. The
real-cargo byte-for-byte test bumps crate-b from 0.1.0 to 0.2.0 past
git copies at 0.1.5 (entry order) and 0.10.0 (reference order).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
@goosewobbler goosewobbler changed the title fix(version): sync Cargo.lock on every Cargo.toml bump path fix(version): sync every committed Cargo.lock that records a bumped crate Oct 22, 2026
Same sweep as #679, run on this branch so merging main resolves cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
# Conflicts:
#	packages/publish/src/stages/prepare.ts
#	packages/publish/src/utils/cargo.ts
#	packages/version/src/core/versionStrategies.ts
…an ignored lock

Crates in different workspaces can share a name (every `tauri init` app
is `app`), and every bump was applied to every lock with a path entry of
that name, so one app's lock took another app's version.

A crate's own lock still takes the crate's bump whatever version it
records. In any other lock, the entry follows a bump only when something
in that lock depends on it (an entry nothing depends on is that lock's
own root or member, a different crate) and it is at the crate's old or
new version; bumps record the manifest's pre-bump version for this.
Same-named bumps to different versions that fit one entry fail the run.

A crate's own lock that git ignores is no longer synced or staged: the
direct-commit release's `git add` refused it and failed the release.
`Git` gains `isIgnored` (`check-ignore -q`); the planner now does its
own discovery, so `findCommittedCargoLockfiles` is gone.

Also: version's syncCargoLockfiles returns void, the unused `git`
parameters and FakeGit's trackedFiles seed are dropped, the duplicate
comment in VersionEngine.run is removed, and tests pin numeric
pre-release ordering and the several-path-entries refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
docs/cli.md lists the code (category `cargo-lock-sync`, exit code 8).
docs/rust.md states which lock entries follow a bump and covers every
reason the sync refuses a lock, not only the other-source one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
findCargoLockfile doesn't read an ancestor workspace's `exclude`, so a
crate excluded from it (a Tauri app's src-tauri, say) got the ancestor's
lock as its own, and its real lock was matched as another workspace's:
its root entry, which nothing depends on, was skipped and left stale. A
lock beside the manifest can only be the crate's own (or an unused
stray), so it now counts as one.

When the manifest records no version (`version.workspace = true`, or
none), the old version now comes from the crate's own lock, so other
locks are still matched on it; only without an own lock does any
version match.

Tests pin both, the any-version fallback, publish reporting the
replaced version, and the versioned-reference match in core.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
…it codes

Only other apps' own crates of the same name are told apart; a different
same-named crate that a lock depends on at the bumped crate's old or new
version is not, so the unsupported list says so and how to avoid it. The
lock-sync error exits 8 from `release` and `standing-pr` but 1 from
`releasekit version`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
findCargoLockfile took the first ancestor `[workspace]` as the crate's
root without reading its `exclude`, so a crate excluded from the repo's
workspace treated that workspace's lock as its own, where every bump is
applied: two excluded Tauri apps named `app` failed the run as ambiguous
in a root lock that has no `app`, and bumping one excluded `app` set the
root workspace's own member `app` to its version.

It now follows cargo: a crate whose manifest has a `[workspace]` is its
own root; otherwise the nearest ancestor workspace that doesn't exclude
it is (an `exclude` path prefix not overridden by a `members` one, both
literal); with none, the crate is its own root. Only the root's lock is
returned, which also drops the nearest-lock fallback for standalone
crates (cargo never reads a non-root ancestor's lock). Checked against
cargo 1.97. `exclude` is read with smol-toml, now a core dependency.

The round-2 "lock beside the manifest" own-lock candidate is dropped: the
new rule returns it whenever it is the crate's lock, and a member's stray
lock is unused by cargo.

The planner's doc now notes that any version matches when neither the
manifest nor the crate's own lock records the old one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
… workspace pointer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
github-actions Bot and others added 2 commits October 23, 2026 00:30
Drop doc comments that restate a name or signature, issue numbers and
history; shorten the rest to the cargo behaviour the code relies on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… crate by path

Name and version can't identify a path crate: two workspaces can each have
a `core` 0.1.0, and bumping one rewrote the other's lock, breaking
`--locked` builds. A lock other than the crate's own now takes a bump only
if its workspace reaches the crate's directory through path dependencies
(target-specific, `workspace = true`, `[patch]`; dev-dependencies of its
own members only), as cargo resolves them. Drops the version heuristic and
`fromVersion`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread packages/core/src/cargo.ts Outdated
Comment thread packages/core/src/cargo.ts Outdated
github-actions Bot and others added 2 commits October 23, 2026 04:13
…tch]

Cargo honours [patch] only in the workspace root's manifest, so following
it from every visited manifest could rewrite an unrelated same-named entry.
And a member outside its workspace's directory points back with
package.workspace; without following it, its real lock went stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also document that `[patch]` in `.cargo/config.toml` isn't followed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@goosewobbler goosewobbler changed the title fix(version): sync every committed Cargo.lock that records a bumped crate fix(version)!: sync every committed Cargo.lock that records a bumped crate Oct 23, 2026
Cargo drops a patch the bumped version no longer satisfies, so following
it could write a lock cargo never would. Support is tracked in #706.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@goosewobbler
goosewobbler merged commit 5713c24 into main Oct 23, 2026
33 checks passed
@goosewobbler
goosewobbler deleted the fix/650-cargo-lock-sync branch October 23, 2026 12:26
@github-actions github-actions Bot mentioned this pull request Oct 23, 2026
goosewobbler pushed a commit that referenced this pull request Oct 23, 2026
…e-commands

Keeps #674's CARGO_LOCK_SYNC_ERROR in the version group of the expanded
exit-code table. With version exiting by its code's family, docs/rust.md
no longer says the standalone command exits 1 for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQN3v9Z4f1mu3xK4Z7K44e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cargo.lock files go stale at every release, so each one needs a manual sync PR

2 participants