Mounting Git commits as folders with NFS

pvtmert1 pts1 comments

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&rsquo;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&rsquo;s a broken WebDav<br>implementation too.

It&rsquo;s pretty experimental (I&rsquo;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&rsquo;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&rsquo;s just that git commits aren&rsquo;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&rsquo;t completely explain how git works (there&rsquo;s a lot more to<br>it than just &ldquo;a commit is like a folder!&rdquo;), 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&rsquo;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&rsquo;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&rsquo;t support symlinks, I think because it uses the io/fs interface<br>and io/fs doesn&rsquo;t support symlinks yet. Looks like that&rsquo;s in progress<br>though. So...

commits rsquo commit folders like bork

Related Articles