Mounting git commits as folders with NFS
Skip to main content
Hello! The other day, I started wondering – has anyone ever made a FUSE<br>filesystem for a git repository where all every commit is a folder? It turns<br>out the answer is yes! There’s giblefs,<br>GitMounter, and git9 for Plan 9.
But FUSE is pretty annoying to use on Mac – you need to install a kernel<br>extension, and Mac OS seems to be making it harder and harder to install kernel<br>extensions for security reasons. Also I had a few ideas for how to organize the<br>filesystem differently than those projects.
So I thought it would be fun to experiment with ways to mount filesystems on<br>Mac OS other than FUSE, so I built a project that does that called<br>git-commit-folders. It works (at least on my computer) with both FUSE and NFS, and there’s a broken WebDav<br>implementation too.
It’s pretty experimental (I’m not sure if this is actually a useful piece of<br>software to have or just a fun toy to think about how git works) but it was fun<br>to write and I’ve enjoyed using it myself on small repositories so here are<br>some of the problems I ran into while writing it.
goal: show how commits are like folders
The main reason I wanted to make this was to give folks some intuition for how<br>git works under the hood. After all, git commits really are very similar to<br>folders – every Git commit contains a directory listing of the files in it,<br>and that directory can have subdirectories, etc.
It’s just that git commits aren’t actually implemented as folders to save<br>disk space.
So in git-commit-folders, every commit is actually a folder, and if you want<br>to explore your old commits, you can do it just by exploring the filesystem!<br>For example, if I look at the initial commit for my blog, it looks like this:
$ ls commits/8d/8dc0/8dc0cb0b4b0de3c6f40674198cb2bd44aeee9b86/<br>README
and a few commits later, it looks like this:
$ ls /tmp/git-homepage/commits/c9/c94e/c94e6f531d02e658d96a3b6255bbf424367765e9/<br>_config.yml config.rb Rakefile rubypants.rb source
branches are symlinks
In the filesystem mounted by git-commit-folders, commits are the only real folders – everything<br>else (branches, tags, etc) is a symlink to a commit. This mirrors how git works under the hood.
$ ls -l branches/<br>lr-xr-xr-x 59 bork bazil-fuse -> ../commits/ff/ff56/ff563b089f9d952cd21ac4d68d8f13c94183dcd8<br>lr-xr-xr-x 59 bork follow-symlink -> ../commits/7f/7f73/7f73779a8ff79a2a1e21553c6c9cd5d195f33030<br>lr-xr-xr-x 59 bork go-mod-branch -> ../commits/91/912d/912da3150d9cfa74523b42fae028bbb320b6804f<br>lr-xr-xr-x 59 bork mac-version -> ../commits/30/3008/30082dcd702b59435f71969cf453828f60753e67<br>lr-xr-xr-x 59 bork mac-version-debugging -> ../commits/18/18c0/18c0db074ec9b70cb7a28ad9d3f9850082129ce0<br>lr-xr-xr-x 59 bork main -> ../commits/04/043e/043e90debbeb0fc6b4e28cf8776e874aa5b6e673<br>$ ls -l tags/<br>lr-xr-xr-x - bork 31 Dec 1969 test-tag -> ../commits/16/16a3/16a3d776dc163aa8286fb89fde51183ed90c71d0
This definitely doesn’t completely explain how git works (there’s a lot more to<br>it than just “a commit is like a folder!”), but my hope is that it makes thie<br>idea that every commit is like a folder with an old version of your code" feel<br>a little more concrete.
why might this be useful?
Before I get into the implementation, I want to talk about why having a filesystem<br>with a folder for every git commit in it might be useful. A lot of my projects<br>I end up never really using at all (like dnspeep) but I did find myself using this<br>project a little bit while I was working on it.
The main uses I’ve found so far are:
searching for a function I deleted – I can run grep someFunction branch_histories/main/*/commit.go to find an old version of it
quickly looking at a file on another branch to copy a line from it, like vim branches/other-branch/go.mod
searching every branch for a function, like grep someFunction branches/*/commit.go
All of these are through symlinks to commits instead of referencing commits<br>directly.
None of these are the most efficient way to do this (you can use git show and<br>git log -S or maybe git grep to accomplish something similar), but<br>personally I always forget the syntax and navigating a filesystem feels easier<br>to me. git worktree also lets you have multiple branches checked out at the same<br>time, but to me it feels weird to set up an entire worktree just to look at 1<br>file.
Next I want to talk about some problems I ran into.
problem 1: webdav or NFS?
The two filesystems I could that were natively supported by Mac OS were WebDav<br>and NFS. I couldn’t tell which would be easier to implement so I just<br>tried both.
At first webdav seemed easier and it turns out that golang.org/x/net has a<br>webdav implementation, which was<br>pretty easy to set up.
But that implementation doesn’t support symlinks, I think because it uses the io/fs interface<br>and io/fs doesn’t support symlinks yet. Looks like that’s in progress<br>though. So...