cd /news/developer-tools/i-optimized-my-bump-version-tool-and… · home topics developer-tools article
[ARTICLE · art-127292] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

I Optimized My Bump Version Tool and Made It 1,000,000x Faster Than Its Python Counterparts.

A developer rewrote the bump2version version-bumping tool in Rust, releasing version 0.2.1 with a branchless lookup-table design, SmallVec-based version storage, and memchr-based scanning. The release adds file watching, manifest auto-detection, and support for pyproject.toml, pom.xml, and go.mod, and benchmarks claim roughly 1,000,000x faster parse-bump-serialize than the Python bump-my-version CLI when counting subprocess and interpreter startup overhead.

by read13 min views2 publishedSep 11, 2026

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.

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.

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:

memchr``.contains() in a loop like an animal",SmallVec`` Vec<u8> for three numbers (major, minor, patch) is an insult to modern CPU cache lines.if major { ... } else if minor { ... } nonsense. One lookup table. One store. Done.Cargo.toml like it's the only file in the world", they observed, correctly. "What about pyproject.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.

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, 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.

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.

strchr and it is unreasonably good at its one job.SmallVec<[VersionPart; 8]>`` Arc<Regex> cache 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:

--watch flag.bump registers a recursive file watcher on all files listed in your .bumpversion.toml. current_version. 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.

bump --watch --bump patch --config-file .bumpversion.toml
[watch] Detected change: Cargo.toml
[watch] Bumping patch: 0.2.1 → 0.2.2

--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:

target/, node_modules/, .git/, etc.) setup.cfg, package.json, build.gradle, Gemfile Six languages. One command.

cd my-monorepo
bump --current-version 0.1.0 --bump patch --detect --dry-run

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.

Every language now has a working example under examples/, each with its own pre-baked .bumpversion.toml.

bump --config-file examples/python/.bumpversion.toml --bump patch --dry-run

bump --config-file examples/nodejs/.bumpversion.toml --bump minor --dry-run

bump --config-file examples/java/.bumpversion.toml --new-version 2.0.0 --dry-run

bump --config-file examples/ruby/.bumpversion.toml --bump major --dry-run

cd examples/multi-lang
bump --current-version 0.1.0 --bump minor --detect --dry-run

6 ecosystems. 1 tool. Still forbids unsafe. We have standards.

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:

wasm32-unknown-unknown, no server required version_ops.rs 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".

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:

detect.rs walk implementation 7 times until it correctly skipped node_modules without accidentally also skipping node_modules_backup (important distinction)SmallVec<[VersionPart; 4]> for typical semver usage (it is 8, empirically) 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.

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 runbump2version --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.

bump --bump patch
cargo bump --bump patch
bump2version --bump patch   # kept for backward compatibility

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.

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.

cargo install bump2version --features rust-binary

This gets you bump, cargo-bump, and bump2version. All 3. No choices required. Just install and bump.

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

bump --bump patch --dry-run
bump --bump minor -n        # same thing, shorter

bump --bump patch --commit --tag

bump --bump minor --commit --message "chore: ship {new_version} 🚀"

bump --bump patch --detect

bump --watch --bump patch

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");
pip install bump-rs
python
from bump_rs import bump_version
print(bump_version("1.2.3", "major"))  # "2.0.0" in ~57µs
npm install bump2version
js
const { bumpVersion } = require("bump2version");
console.log(bumpVersion("1.2.3", "patch")); // "1.2.4"

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:

alpha.1 → alpha.2 → beta.1 → rc.1 → stable lifecycle, first-classdetect targets.NET (*.csproj), PHP ( composer.json), Swift ( Package.swift), Elixir ( mix.exs) 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.

bump2version 0.2.1 started as a performance exercise and became an ecosystem. What was a single binary is now:

no_std support for the corebump-rs) for Pythonistas who want the speed without the syntax cargo-bump, bump2version cargo install bump2version --features rust-binary → bump → ship → repeat → be 1,000,000× faster than Python → sleep → repeat 🦀

bump2version is the world's fastest version bumper written entirely in 100% safe Rust, with no_std support, native Python and Node.js bindings, and a cargo bump subcommand 🗿.

Platform Command
Rust binary cargo install bump2version --features rust-binary
Cargo subcommand cargo bump --help
Docker docker pull wiseaidev/bump2version
Debian/Ubuntu Download .deb fromGitHub Releases
RHEL/Fedora Download .rpm fromGitHub Releases
Windows Download bump.exe fromGitHub 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.

bump2version automates semantic version management for any project regardless of language. It:

major.minor.patch). 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.

── more in #developer-tools 4 stories · sorted by recency
github.com · · #developer-tools
Valknut
── more on @bump2version 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/i-optimized-my-bump-…] indexed:0 read:13min 2026-09-11 ·