Hello, fellow version-bumping enthusiasts, sleep-deprived Rustaceans, and accidental software archaeologists who just found out that bumpversion is a thing 👋!
So there I was, staring at my terminal at 2AM, trying to release version 0.1.0 of something. I typed bump-my-version patch, pressed Enter, and watched my CPU fan spin up like it was launching a SpaceX rocket. One Second later, one second, it bumped a number. One tiny number. 0.1.0 → 0.1.1.
Then I did what any rational developer would do: I rewrote it. In Rust. From scratch. With Python and Node.js bindings. And a CLI. And nostd support. And gix for pure-Rust git operations.
The result? bump2version 0.2.0: a version bumper that is legitimately, measurably, embarrassingly \~10,000x faster than the Python CLI it replaces.
Glad you asked. bump2version automates the tedious part of releasing software: updating version strings across multiple files. You know, the part where you manually grep through Cargo.toml, package.json, pyproject.toml, CHANGELOG.md, and your README, change 1.2.3 to 1.2.4 in 11 different places, forget one, push, CI fails, and you cry quietly into your coffee?
bump2version does all of that for you: Parses version strings using a fully configurable regex (defaults to semver major.minor.patch). Bumps any component you ask it to: major, minor, patch, or custom cyclic stages like alpha → beta → stable. Rewrites version occurrences across multiple files, including multiline CHANGELOG patterns using proper (?ms) DOTALL + MULTILINE semantics. Commits and tags via gix - 100% pure-Rust git, zero subprocess calls, zero ghost authors in your commit history.
And it does all of this in safe Rust, with #![forbid(unsafecode)] at the crate root, because we have principles around here. Or at least we pretend to.
Here's the fun part: bump2version isn't just a Rust crate. It's three tools pretending to be one in a trench coat.
One Rust core. Three ecosystems. Zero Python subprocesses. Ferris the crab is now a polyglot, and honestly? Good for them. 🦀
Let me be transparent about one thing: I did not architect the full system design for this project alone.
No, I had help. Specifically, I reached out to some very professional consultants.
They arrived at my door at 3AM with a whiteboard and a very detailed opinion on Arc caching strategies. Their key architectural recommendation, which I followed verbatim after reviewing it at gunpoint (metaphorically, probably), was the thread-safe Arc cache. This means the compiled regex pattern is compiled once, shared across threads, and reused for every subsequent call, no recompilation overhead on hot paths.
The result: version bumping in \~57 microseconds from Python land. Not 57 milliseconds. Not 57 seconds. 57 microseconds. The kind of number that makes you wonder what the Python version was doing during its 585 millisecond run.
Okay. Let's talk benchmarks. Because this is the part of the blog post where I get to paste a table and feel deeply smug about it.
These are real numbers, measured on x86-64 Linux (CPython 3.12, 3-sigma filtered timeit):
| Library | patch | minor | major | | -------------------------------------- | ----------- | ----------- | ----------- | | bump-rs (Rust, Arc cache) | \~57 µs | \~54 µs | \~53 µs | | bump-my-version (Python library) | \~79 µs | \~95 µs | \~72 µs | | Pure Python (re.compile + int()) | \~3.6 µs | \~2.2 µs | \~2.2 µs | | bump-my-version CLI (subprocess) | \~585 ms | \~585 ms | \~585 ms |
Now, I can already hear you: "But the pure Python version is actually faster for single calls!"
Yes. You're right. The \~50 µs PyO3 FFI overhead means that if you're bumping exactly one version string in isolation on a warm Python interpreter, pure re.compile + int() will smoke us.
