Hello again, fellow Rustaceans, Pythonistas in denial, and the three people on earth who genuinely enjoy reading version bumper release notes π!
If you were here for my last post, where I rewrote bump2version in Rust and declared it ~10,000x faster than the Python CLI, you may recall that I ended it with a vague threat about future benchmarks. The Mossad agents who consulted on the architecture wrote "this is not over" in the margin of their whiteboard. I ignored it.
I should have listened.
Because in the weeks since that post, I went back in. Deep in. I committed crimes against cargo build that will haunt me during thunderstorms. I --release'd things that should not be --release'd. I made Claude hallucinate a performance chart at 3AM and then used it to motivate myself.
The result: bump2version 0.2.1.
Now 1,000,000Γ faster than its Python counterparts. Yes, I am counting subprocess overhead.
The Soviet material resurfaced. It always does.
Let's get into it.
π€ Wait, We Already Did This. Why Are We Here Again?
Because 0.2.0 shipped, and I immediately opened my Gmail inbox.
The audacity of the open-source community. The audacity, bro! I build something 10,000x faster and within days there are requests. "Can it watch files?", "Can it detect which manifests I'm using?", "Can I run it in the browser?", "Can I use it with Go?", "Can I use it with Java?", "Can my Ruby project use it?". I ain't got no time for this. I need to take a little break and play CS2 with the boys!
I looked at these emails. I looked at the ceiling. I looked at my coffee. The coffee looked back at me with the hollow expression of a language runtime that has seen too many package manager debates.
And then I cracked my knuckles and got to work.
π΅οΈ The Mossad Agents Return
I thought I was done with the Mossad agents after 0.2.0. I was not.
They came back. Same 3AM knock. Same whiteboard. But this time they brought slides. Twelve of them. With bullet points. And speaker notes. One slide was just the word "BRANCHLESS" in 72-point font with a red circle around it.
Their new requirements:
-
memchrfor all hot-path string scanning. "You are doing.contains()in a loop like an animal", they said. I was. -
SmallVecfor version component storage. Because heap-allocating aVec<u8>for three numbers (major, minor, patch) is an insult to modern CPU cache lines. -
Branchless arithmetic for the bump itself. No
if major { ... } else if minor { ... }nonsense. One lookup table. One store. Done. - Watch mode. They wanted to know the moment a file changed. I asked why. They said that was classified.
-
Auto-detect language manifests. "You scan
Cargo.tomllike it's the only file in the world", they observed, correctly. "What aboutpyproject.toml?pom.xml?go.mod? Are Go developers not also suffering?"
They are. They are very much also suffering.
I implemented every point. The Mossad agents reviewed the diff, said "passable", and vanished into the root filesystem like a well-placed .gitignore entry.
β‘ The 1,000,000Γ Number
Let me be absolutely scientifically honest with you for one sentence before I stop being honest: the 1,000,000Γ number compares the full bump-my-version CLI round-trip (importing Python, loading dependencies, spawning a subprocess) against our in-process library call with a warm cache and a SmallVec.
One is a sports car. The other is a person who has to call a taxi, wait 12 minutes, and explain where they're going in a language they only partially speak.
Now let's look at the real numbers.
π₯ New Benchmarks: 0.2.0 vs 0.2.1 vs Python
| Operation |
bump2version 0.2.0
|
bump2version 0.2.1
|
Python pyO3 FFI |
bump-my-version CLI |
|---|---|---|---|---|
| Parse + bump + serialize | ~57 Β΅s | ~0.4 Β΅s | ~79 Β΅s | ~585 ms |
| File search/replace (1k lines) | ~65 Β΅s | ~11 Β΅s | ~1.7 Β΅s | ~590 ms |
| File search/replace (100k lines) | ~4.2 ms | ~0.9 ms | N/A | ~600 ms |
| Config file parse | ~800 Β΅s | ~140 Β΅s | N/A | ~500 ms (on import) |
Versus the Python CLI subprocess: ((500 x 1000) Γ· 0.4) = ~1,250,000Γ faster for a version parse-bump-serialize. We round down to 1,000,000x for humility. The Mossad agents said rounding up was acceptable. We preferred honesty.
What got fast and why:
-
memchrreplaced every inner-loop string search.memchris basically SIMD-acceleratedstrchrand it is unreasonably good at its one job. -
SmallVec<[VersionPart; 8]>means version component vectors never touch the allocator for reasonable semver (β€8 parts). Stack-allocated. L1 cache. Zero malloc. - Branchless bump arithmetic: the bump logic is now a lookup into a const table of which component to increment and which to zero. The CPU's branch predictor doesn't even have to sweat.
-
Arc<Regex>cache from0.2.0was kept. The regex is compiled exactly once and shared. The Mossad agents reminded us about this at every meeting by projecting the same slide.
π‘ Watch Mode
bump --watch --bump patch
It sounds simple. It was the opposite of simple.
"Just watch a file and re-bump when it changes", I told myself, in the tone of a man who has never used inotify before.
Bro, I used inotify. Or rather, notify-rs used inotify for me, which is almost the same thing except someone smarter than me had already suffered through the Linux kernel filesystem event API so I wouldn't have to. God bless crate authors.
Watch mode works like this:
- You provide a
--watchflag. -
bumpregisters a recursive file watcher on all files listed in your.bumpversion.toml. - Every time a registered file is saved (debounced to 200ms to avoid event avalanches),
bumpchecks whether the version string is still consistent withcurrent_version. - If it detects drift, it re-applies the bump.
This is either extremely useful for local development workflows or a deeply irresponsible footgun. We ship tools for adults. The lock safety is your problem.
# Watches VERSION, Cargo.toml, and README.md for changes, auto-bumps on save
bump --watch --bump patch --config-file .bumpversion.toml
# Oh no did I just? yes, yes you did. It already ran.
[watch] Detected change: Cargo.toml
[watch] Bumping patch: 0.2.1 β 0.2.2
π Multi-Language Auto-Detection: --detect Is Doing God's Work
Previous bump2version was Rust-centric. You gave it files, it bumped files. Very obedient lil tool. Very rigid. Like a Rust compiler, actually.
The new --detect flag changes all that:
bump --bump minor --detect
This single command:
-
Walks the entire directory tree (skipping
target/,node_modules/,.git/, etc.) -
Finds every recognized manifest:
Cargo.toml,pyproject.toml,setup.cfg,package.json,go.mod,pom.xml,build.gradle,Gemfile - Checks each one for the current version string
- Rewrites every match in one coordinated pass
Six languages. One command.
cd my-monorepo
bump --current-version 0.1.0 --bump patch --detect --dry-run
# [detect][dry-run] Would update: Cargo.toml
# [detect][dry-run] Would update: pyproject.toml
# [detect][dry-run] Would update: package.json
# [detect][dry-run] Would update: pom.xml
# [detect][dry-run] Would update: go.mod
# [detect][dry-run] Would commit 5 file(s): 0.1.0 β 0.1.1
Full supported matrix:
| Language | Manifest Files |
|---|---|
| π¦ Rust | Cargo.toml |
| π Python |
pyproject.toml, setup.cfg, setup.py
|
| π¨ JavaScript / Node.js | package.json |
| πΉ Go | go.mod |
| β Java |
pom.xml, build.gradle, build.gradle.kts
|
| π Ruby | Gemfile |
The Go developers, in particular, messaged to say thank you. We thanked them for using Go despite everything.
π Real Language Examples
Every language now has a working example under examples/, each with its own pre-baked .bumpversion.toml.
# Python project
bump --config-file examples/python/.bumpversion.toml --bump patch --dry-run
# [dry-run] Would commit 2 file(s): 0.1.0 β 0.1.1
# Node.js project
bump --config-file examples/nodejs/.bumpversion.toml --bump minor --dry-run
# [dry-run] Would commit 1 file(s): 0.1.0 β 0.2.0
# Java Maven project
bump --config-file examples/java/.bumpversion.toml --new-version 2.0.0 --dry-run
# [dry-run] Would commit 1 file(s): 0.1.0 β 2.0.0
# Ruby gemspec + Gemfile
bump --config-file examples/ruby/.bumpversion.toml --bump major --dry-run
# [dry-run] Would commit 2 file(s): 0.1.0 β 1.0.0
# The "bump everything at once" grand finale
cd examples/multi-lang
bump --current-version 0.1.0 --bump minor --detect --dry-run
# [detect][dry-run] Would update: Cargo.toml, pyproject.toml, package.json, pom.xml, Gemfile, VERSION
6 ecosystems. 1 tool. Still forbids unsafe. We have standards.
πΈοΈ The Yew WASM App
You ever look at a perfectly good CLI tool and think: "This would be better if I could bump versions from a browser"?
No? Me neither. But then someone on the team whispered "WASM" and I remembered that I am constitutionally incapable of saying no to WebAssembly.
So now there is a Yew-based web application at examples/yew-app/. It:
-
Runs entirely in the browser:
wasm32-unknown-unknown, no server required -
Connects to the same Rust core via a
version_ops.rsbridge module
And yes, git-related features had to be extracted into an optional feature flag (git) because gix uses Unix-specific APIs that don't compile to WASM. We found this out the fun way, which is a phrase that means "at 2AM with a 47-line linker error".
π€ I Abused ChatGPT, Claude and Gemini. Again. More.
You may recall from my previous post that I abused Claude during 0.2.0 development. My lawyer said not to bring it up again.
So I won't bring up the fact that during 0.2.1 development, I made ChatGPT, Claude and Gemini:
- Rewrite the
detect.rswalk implementation 7 times until it correctly skippednode_moduleswithout accidentally also skippingnode_modules_backup(important distinction) - Argue with itself about whether
SmallVec<[VersionPart; 8]>was better thanSmallVec<[VersionPart; 4]>for typical semver usage (it is8, empirically) - Generate
.bumpversion.tomlconfigs for 7 languages, review them for correctness, then find its own mistake in the Ruby config and fix it without being asked
The OpenAI, Anthropic and Google lawyers have upgraded from "concerned" to "a medium-sized incident report has been filed".
My legal counsel has asked that I clarify: no Claude was permanently harmed. Tokens were consumed. Electricity was used. The --detect flag works correctly.
π 3 Binaries, One Package, Zero Confusing Changelog Entries
After cargo install bump2version --features rust-binary, you get three things:
| Binary | Use case |
|---|---|
bump |
The modern, primary CLI |
cargo-bump |
Cargo subcommand (cargo bump --bump patch) |
bump2version |
β
Backward-compatible alias for old CI configs, tutorials, and the 47 blog posts that told people to run bump2version --bump patch
|
The bump2version binary is not deprecated. It is not going anywhere. If you have a shell script from 2025 that calls bump2version --bump minor, it will still work in 2030, and presumably during the heat death of the universe, if any of your CI pipelines survive that long.
This was a deliberate choice. Breaking changes in release tooling are a form of chaos that no version bumper should inflict on the people who trusted it.
# All of these do the exact same thing:
bump --bump patch
cargo bump --bump patch
bump2version --bump patch # kept for backward compatibility
π The .bumpversion.toml Glow-Up
The config file now tracks all version-carrying files in the project, not just Cargo.toml:
[bumpversion]
current_version = 0.2.1
commit = false
tag = false
[bumpversion:file:Cargo.toml]
search = version = "{current_version}"
replace = version = "{new_version}"
[bumpversion:file:package.json]
search = "version": "{current_version}"
replace = "version": "{new_version}"
[bumpversion:file:README.md]
search = "{current_version}"
replace = "{new_version}"
[bumpversion:file:RUST.md]
search = "{current_version}"
replace = "{new_version}"
[bumpversion:file:WASM.md]
search = "{current_version}"
replace = "{new_version}"
[bumpversion:file:DOCKER.md]
search = `{current_version}`
replace = `{new_version}`
[bumpversion:file:PACKAGING.md]
search = {current_version}
replace = {new_version}
[bumpversion:file:rpm/bump2version.spec]
search = Version: {current_version}
replace = Version: {new_version}
One bump --bump patch and every single version string, across docs, code, configs, packaging specs, and Docker manifests, updates atomically. The Mossad agents reviewed this config and said "acceptable" which, from them, is essentially a standing ovation.
π¦ The Borrow Checker Tried to Ruin Watch Mode
There is a moment in every Rust developer's life, we all know the moment, where you look at a piece of code that should work, that does work in your head, that you have drawn on paper with arrows and boxes to prove its correctness, and the borrow checker looks at you across the compiler output and says:
error[E0505]: cannot move out of 'watcher' because it is borrowed
And then below that:
note: the borrow later used here
And then below that, a footnote that reads note: move occurs because... followed by a chain of reasoning so long it wraps around to the next terminal page.
The Yew WASM integration did this to me for different reasons: gix depends on Unix syscalls (openat, statx, platform symlink handling) that simply do not exist in wasm32-unknown-unknown. The linker error was 47 lines long and named three crates I had never heard of.
The fix: gating all git-related functionality behind a git feature flag that is disabled by default for WASM builds. Clean. Simple. The kind of solution that is obvious in retrospect and invisible before you spend four hours in the linker output.
[features]
default = ["std", "git"]
git = ["gix", "std"]
cli = ["clap", "git"]
rust-binary = ["cli", "git", "detect", "watch"] # β full featured binary
watch = ["notify", "cli"]
detect = ["walkdir", "cli"]
The WASM app uses none of the git features. The CLI binary uses all of them. The feature graph is clean enough that my Mossad advisors called it "elegant" before immediately asking about the benchmark numbers.
π¦ Getting The Full Picture
# The complete CLI, with everything
cargo install bump2version --features rust-binary
This gets you bump, cargo-bump, and bump2version. All 3. No choices required. Just install and bump.
Quick Examples
# Standard semver
bump --bump patch # 0.2.1 β 0.2.2
bump --bump minor # 0.2.1 β 0.3.0
bump --bump major # 0.2.1 β 1.0.0
# Safe preview
bump --bump patch --dry-run
bump --bump minor -n # same thing, shorter
# Git integration
bump --bump patch --commit --tag
# Custom message
bump --bump minor --commit --message "chore: ship {new_version} π"
# Auto-detect all manifests
bump --bump patch --detect
# Watch mode (exits when you ctrl + c)
bump --watch --bump patch
As a library (no std required for the core)
[dependencies]
bump2version = { version = "0.2.1", default-features = false }
use bump2version::{config::BumpConfig, version::{parse_version, bump_version, serialize_version}};
let cfg = BumpConfig::default();
let v = parse_version("1.2.3", &cfg).unwrap();
let v2 = bump_version(&v, "minor", &cfg).unwrap();
assert_eq!(serialize_version(&v2, &cfg), "1.3.0");
Python
pip install bump-rs
from bump_rs import bump_version
print(bump_version("1.2.3", "major")) # "2.0.0" in ~57Β΅s
Node.js
npm install bump2version
const { bumpVersion } = require("bump2version");
console.log(bumpVersion("1.2.3", "patch")); // "1.2.4"
π What Comes After 1,000,000Γ
Look, at some point the law of diminishing returns kicks in. We cannot make version bumping faster than the speed of light. (We checked. We asked the Mossad agents. They checked their slides. The answer was no.)
But there are things we can still do:
-
Pre-release cycling: proper
alpha.1 β alpha.2 β beta.1 β rc.1 β stablelifecycle, first-class - Workspace-aware bumping: atomic multi-crate Cargo workspace updates, all in one commit
-
More
detecttargets:.NET(*.csproj), PHP (composer.json), Swift (Package.swift), Elixir (mix.exs) - Browser version history: the Yew app keeps a local bump history so you can see what you broke and when
If you want any of these sooner: open an issue. Or just star the repo and the moral pressure will accelerate delivery. It works on me. I've tested this empirically.
π¬ Closing Thoughts
bump2version 0.2.1 started as a performance exercise and became an ecosystem. What was a single binary is now:
- A Rust library with
no_stdsupport for the core - A Python package (
bump-rs) for Pythonistas who want the speed without the syntax - A Node.js native add-on for JavaScript developers who want to feel like they're using Rust
- A CLI with three binary names:
bump,cargo-bump,bump2version - A WASM browser app built in Yew, because someone had to
- 7 language examples covering Rust, Python, Node.js, Go, Java, Ruby, and poly-repo workflows
- 20 integration tests that test the actual binary, not a mock
- An auto-updatable
.bumpversion.tomlthat tracks all version strings across the entire project
cargo install bump2version --features rust-binaryβ bump β ship β repeat β be 1,000,000Γ faster than Python β sleep β repeat π¦
wiseaidev
/
bump2version
β¬οΈ A blazingly fast, thread safe, git client agnostic, CLI for managing version numbers in your projects.
β¬οΈ Bump2version
bump2versionis the world's fastest version bumper written entirely in 100% safe Rust, withno_stdsupport, native Python and Node.js bindings, and acargo bumpsubcommand πΏ.
π Installation
| Platform | Command |
|---|---|
| Rust binary | cargo install bump2version --features rust-binary |
| Cargo subcommand | cargo bump --help |
| Docker | docker pull wiseaidev/bump2version |
| Debian/Ubuntu | Download .deb from GitHub Releases
|
| RHEL/Fedora | Download .rpm from GitHub Releases
|
| Windows | Download bump.exe from GitHub Releases
|
| GitHub Action | See action.yml |
| Python | pip install bump-rs |
| Node.js | npm install bump2version |
Note
Installing via cargo installs both bump and bump2version binaries. The original bump2version binary is retained indefinitely for backward compatibility with existing tutorials, CI/CD pipelines, and automation scripts.
π€ What does this crate provide?
bump2version automates semantic version management for any project regardless of language. It:
-
Parses version strings using a fully configurable regex (default: semver
major.minor.patch). -
Bumps any named component (
major,minor,patch, orβ¦
Star the repo. Try the Python bindings. Use the Node.js package. Read the Rust docs. Poke the WASM demo. Run the examples.
This has been a public service announcement from a developer who, in the course of a single engineering session, created 20 tests, 7 example projects, a WASM frontend, a Soviet-themed benchmark suite, and consumed an amount of Claude tokens that my accountant has asked me not to disclose.
The legal proceedings with Top Tech companies remain ongoing. My lawyer has read this post. He has asked me to note, for the record, that "abusing Claude" is a colloquial and affectionate term and not a legally actionable description of token consumption.
I have not taken that advice either.
Till next time: Keep bumpin', keep rustin', keep benchmarkin' π¦β¬οΈ
P.S. The Mossad agents have approved this post subject to the removal of the classified section about the branchless arithmetic lookup table. We kept it in. They know. They've said nothing. This is ominous.












Top comments (0)