sailorfe

2026 Feb 04 – Git and semantic versioning

I've been using Git for eight years, though half of those were just me abusing GitHub Pages for my decidedly non-tech portfolio. It's only since switching to desktop Linux four years ago that I actually paid attention to how OSS projects I use manage versioning through a combination of Git branches and tags along with web UI features like releases. Now that I maintain production code, I'm thinking about what I've taken from being nosy and how setuptools-scm helps me with Python packaging.

The semantic versioning specification by Tom Preston-Warner is ubiquitous, though its implementation varies from project to project, and TPW has written about his observations since. My crude description is a semantic version number goes x.y.z, where z patches are often bug fixes or enhancements that most users wouldn't notice, y minor updates may add new features but retain backwards compatibility, and x major releases break something or significantly alter the UI or API.

The following public repositories use SemVer with Git in different ways, which are all colored by the nature and scale of the project.

Neovim

I build Neovim from source because I use Debian 13 on all my devices and my config is optimized for nvim>=v0.11 while the Trixie repository has 0.10.4.

Neovim has only shipped minor versions, so their branches are named release-0.Y and master. Tags track patches as v0.y.Z and the mutable labels stable and nightly. As of posting, stable and v0.11.6 point to the same commit from last week.

When I build from source, I checkout the stable tag in detached HEAD state. All three of my computers have had fresh system installations in the last year, so my desktop and devbox are at v0.11.3 and my ThinkPad most guilty of distrohopping is at v0.11.6 (just last week!). I've never been one to go for nightly builds on purpose, though I have built Neovim from master on accident before, which names itself v0.12.0-dev if you run :version.

Branching off at minor releases is interesting. Every tag that starts v0.11.* stays on the release-0.11 branch, so I'm curious how they differentiate patches to the current release from new features tagged nightly. As TPW says, "Major Version Numbers are not Sacred", but Neovim has never had one, so these version bumps point to breaking changes while most release notes for patches are small new features or fixes.

detour: xero's lush.nvim themes

Xero Harrison is a Neovim celebrity to me and the reason I feel emboldened to make weaboo Neovim colorschemes, but his branching is so bizarre. Take evangelion.nvim, whose branches are:

This is chaos to me, but he does make use of tags and GitHub releases. I copied this branch format for my own colorschemes at first, but I've switched to something less convoluted and informed by tokyonight.nvim. That said, my colorschemes are such small projects I've never stuck with a versioning system. (ykw after posting this i'll inaugurate them all to v1.0.0)

Charm

I would love to be a GOpher and lurk around the Charm ecosystem like a vampire, though these days I really only use VHS and Freeze. Across the organization, Charm tags patches vx.y.Z. Their branches tend to have the names of WIP features and fixes, like add-copy-to-clipboard, improve-error-msg and bunni-improve-error-msg on Freeze. Most of their activity is on Crush at a whopping 95 branches.

Seeing branches that have dev's names on them makes it clear that these are projects with many authors and with a lot of peer review and mentorship. I can imagine how feature branches might get added and pushed to remote after a Slack discussion or being kicked around a Trello board, or whatever they use.

Linux(es)

For comparison, the Linux kernel currently has just the one master branch (zero stale) with 918 tags as of today, but we're talking about a public repository whose commits date back to 2002.

Alpine Linux's source has 19 branches named in a Neovim-like manner as X.y-stable going back to 3.9-stable. Their tags are a little untamed, a mix of SemVer patches and dates like v20260127.

Source code-managed versioning

I started using setuptools-scm because I wanted to dogfood pre-releases from Codeberg's private PyPI index without cloning my repos to my client machines. SCM stands for "source code-managed," and setuptools-scm "extracts Python package versions from git or hg metadata instead of declaring them manually." I didn't know Mercurial was alchemical like that (see: my handle) until now, but in practice I tag working (I suppose "nightly") versions vX.y.z-dev. All of my pyproject.toml have a section like this:

[tool.setuptools_scm]
version_scheme = "guess-next-dev"
local_scheme = "no-local-version"

[project]
name = ""
dynamic = ["version"]

(See their docs for more on these.)

On docs-as-code

I'm test-driving a more Neovim or Alpine-like branching scheme in my Markdown cat Meow: 0.Y-stable for whatever is on PyPI right now while main takes merges from a local, unpublished branch.

Ephem has more commits, and I haven't prefixed any of my version tags with v. The tags would be tedious and likely pointless to retcon, as it were, but I will be more deliberate starting from the next minor release. However, being hosted on Codeberg, ephem has funny branches due to a lack of native CI that works with Codeberg Pages for the docs. I looked into Woodpecker for this since Codeberg has an instance, but for now I do it manually with a Makefile command that checks out and empties an orphaned branch called pages, then builds and pushes the static site. This is similar to the pre-GitHub Actions practice of deploying from a gh-pages branch.

This Codeberg Pages puzzle raises a question of docs-as-code. In modern Python projects, this is a common filetree:

.
|-- dist/
|   |-- pkg-x.y.z.whl
|   `-- pkg-x.y.z.tar.gz
|-- docs/
|-- src/
|   |-- pkg/
|   |   `-- main.py
|   `-- pkg.egg-info/
|-- tests/
|-- pyproject.toml
`-- README.md

where pyproject.toml should tell your build backend where to look for source code (src/pkg/) to package into dist. It has no relation to what goes on in docs. However, because my own projects are like, I don't know, Tame Impala (it's just me), I consider documentation tied to minor releases (x.Y.z) if they include new features. If you make minor edits to documentation, through Git tagging and something like SCM they become part of a batch of commits under a patch (x.y.Z). The array of one-line Git commit message prefixes I use are:

In the case of pyswisseph, whose versioning scheme added a fourth value since the first three came from the C Swiss Ephemeris, I considered revamped documentation, from technical writing to the build itself, worthy of a patch.

Recent posts

archive