2023-10-20: Fossil

I basically grew up using git, which means that the way my brain thinks about version control is fundamentally git-shaped and I don't find its command set as confusing as most people do. It's been pretty comfortable for me for a long time, and although I've tried a handful of other version control systems (mostly out of curiosity), I've never run into one that I liked more than git. Over the years, I've tried:

So I'd basically been stuck with git. A couple of years back, I came across Fossil:

Had a quick read over it, mentally filed it as Yet Another Distributed Version Control System, and forgot about it and kept using git... but a couple of things about git kept bothering me. One big one is that git, despite being a distributed VCS, has quite a centralized ecosystem. Your source tree is in this very pleasant replicated versioned store, but your bugs, your wiki, your project website, your discussion groups - those are all still stored in one or many centralized services. While it's easy to move a source repo from github to sourcehut, moving the issue tracker and wiki isn't so straightforward. Another is that because git doesn't actually store that ancillary "project stuff", it isn't versioned at all, and changes to it don't show up in history - you can't, say, mark a bug as fixed in the commit that fixes it without some kind of external automation.

This is one of the things that I really liked about Monotone, back when it existed. Monotone stored, versioned, and signed all changes to all of your project state as part of its repository. Neat! Doing that also meant Monotone had to solve the harder UI problem of how to present all of that stuff, and impose at least some policy decisions about what shape it was. Git stays away from that and just versions source code, which makes it simpler to understand (I guess) at the price of every real use of git having a lot of other stuff stapled to it.

So, into Fossil. Fossil is an integrated system like Monotone was, but also does a bunch of stuff differently from both Monotone and any other VCS I've ever used. This post is about my experiences with it.

Repo Structure

In git, the default place for your repository to live is inside the source tree itself, in a directory usually named .git. That's convenient for some things, like being able to move code around with its history, but inconvenient for other things, like being able to check that all of your git repos have been recently pushed. Fossil instead keeps its repository state in a separate file, which can live anywhere you like; the documentation advises you to keep all your fossil repos in one directory, which they call the "museum" (get it? that's where valuable fossils go!). Each fossil repo is a single sqlite database, rather than a tree of files in bespoke formats, which has some interesting consequences, like it being very easy to make encrypted fossil repos. The fossil tooling also keeps track of all the repos you've ever opened or used on your system, and there's a command called fossil all which runs a subcommand on every fossil repo you have, so you can do something like:

fossil all sync
to sync all your repos with their remotes. Speaking of which...

Syncing

Git has this idea that your repository is a clone or fork of some existing upstream repository, which you periodically push and pull changes to or from. That upstream repository is generally a public one hosted somewhere like github, and it is a different kind of thing from your local checkout: it has a web frontend, it has remote access via ssh or https, and it probably doesn't have an actual working tree, only the contents of the .git directory. In Fossil, all repos are fundamentally of the same type - they are all "just" sqlite databases conforming to a specific schema that know how to bidirectionally sync changes with each other. The fossil repo you have on your laptop is one fossil serve away from being public. In fact, fossil ui (the fancy local ui for fossil) works by just starting that server bound to localhost and with auth turned off. Syncing is done by either hitting a web API exposed by that server, or by sshing to it, running the fossil program, and feeding it changes - which is exactly what fossil sync does. Fossil's default behavior is actually to automatically sync changes bidirectionally whenever you make them, which I think is quite nice.

Staging

One of the things new users often find difficult about git is its concept of "staging". In a given git repo, you can have changes in your work tree that are either "staged" for commit or not, and if you run git commit, only those changes that were staged are incorporated. Subcommands like git add stage changes, but a lot of people just use git commit -a, which automatically stages every unstaged change before committing. Personally I think this is a bad default behavior - occasionally one wants to commit only a subset of changes, but it's way more common to accidentally forget to stage a change (like adding a new file) and produce a broken commit. Fossil's default is to commit all changes in the working tree, although it isn't so aggressive as to automatically add new files to the repo. If you really want to commit only certain files, you have to specify them, like fossil commit file1 file2 file3. Fossil has no concept of staging changes in the git sense, which makes it easier to understand what's going on (imo).

UI

Fossil's UI is nicer in most ways. I know this is semi-subjective, but I am a longtime git user and I still think fossil's is better :). Specifically, fossil stays away from the unpopular git-ism of having the same command serve multiple purposes, like git checkout does, and instead generally has commands that do a single thing. It also has good and concise help texts for every command, whereas git help whatever tends to open a combination manual page and PhD thesis that can only be understood by someone who has meditated deeply on the fundamental nature of directed graphs.

Fossil is also a lot chattier than git, which is either good or bad depending on your tastes. For example, fossil status tells you stuff like "the path to the fossil config database" every time you run it, which seems excessive. Still, it's kind of nice that (for example) fossil sync tells you how many artifacts it's sending and how much network bandwidth it's using.

Workflow

Here's how I use Fossil, in case you find yourself wanting to try it. The documentation on the Fossil wiki is excellent so go have a look if you're curious - there are many possible ways to use it.

First, we make a new repository:

fossil init ~/museum/project.fossil
That creates a new, blank repo in ~/museum/project.fossil. Next, we'll want to make a checkout of the repo somewhere so we can actually work on it (note that it's perfectly file to have multiple checkouts of the same repo). We do that like so:
cd ~/project
fossil open ~/museum/project.fossil
Now we might want to configure our project, to give it a name or something, so we'll optionally fire up the fossil UI:
fossil ui
That will load the Fossil management UI in a web browser where we can tweak or fiddle as we desire - or experts can get in there using fossil config instead. Anyway, once we're happy with the repo state, we can do some creating:
edit index.html
edit site.css
And once we're pretty pleased with the result, we can add those files and commit them:
fossil add index.html site.css
fossil commit
Voila! Our first commit, and if we fossil timeline we'll see it shows up in there. If we happened to want to go back to an old version, or see an old version of any specific file, we'd use fossil update or (more rarely) fossil checkout to do that.

Replicas

So far so good for the local workflow, but we want to make copies and push changes to them! Ideally remote copies, in case of a local disaster. To do that, we'll install fossil on some convenient remote machine, then on our local machine, add it as a remote:

fossil remote add vps ssh://host/path/to/file.fossil
If you try to just sync with that remote it won't work, though - the repo doesn't yet exist at all on the remote machine. First, we need to get the repo file over there via rsync:
rsync ~/museum/project.fossil vps:path/to/file.fossil
and now we'll be able to push/pull to it. We don't need a separate server to do this kind of syncing. If we were feeling ambitious, we could even run fossil serve on the remote machine to expose the repo to the internet :)

Closing Thoughts

I like Fossil. It makes a bunch of different choices than git, and in general I've found it a much better fit for the kind of small-scale project work I like doing. Git is fast and performs well even on massive repos, but it gets that speed by being complicated, arcane, and clever. Fossil feels simple and straightforward in comparison, and has the features I want for my own work. I've switched to it for all my own project work already, although I'm sure I will still be using git at work forever. Thanks for reading!

Postscript: this is my first HTML blog post, rather than plain text. What do you think? Is the CSS too much?