Mount a Git repository as a filesystem and browse its entire history like normal directories.
cd into a commit. cat a file as it existed then. diff -r two commits with plain Unix tools.
$ timefs mount ~/code/linux /mnt/linux &
$ cat /mnt/linux/at/v6.1/Makefile | head -3
# SPDX-License-Identifier: GPL-2.0
VERSION = 6
PATCHLEVEL = 1
$ cd /mnt/linux/at/HEAD~40
$ cat kernel/sched/core.c | wc -l
11834
# A commit diff, using nothing but diff:
$ diff -r /mnt/linux/at/HEAD~1 /mnt/linux/at/HEADBecause every revision is just a directory, the entire Unix toolbox —
grep, diff, find, rsync, your editor, ripgrep — works across Git
history with zero new syntax to learn.
Git history is trapped behind Git's own porcelain. Want the tree at HEAD~40?
git checkout mutates your working copy, or you memorize git show <rev>:<path>
one file at a time. timefs makes history a first-class filesystem: read-only,
instant, and browsable with tools you already have.
- Time-travel by
cd—at/HEAD~40/,at/v1.0/,at/main/,at/<sha>/. - Diff commits with
diff—diff -r at/A at/Bis a real commit diff. - Grep across a point in time —
rg TODO /mnt/repo/at/v2.0. - Never touches your repo — mounted strictly read-only; your working tree and object store are untouched.
- Fast on huge repos — content-addressed caching, lazy materialization,
nothing walked until you
cdinto it.
# From crates.io
cargo install timefs
# From source
git clone https://github.com/itsbryanman/timefs
cd timefs
cargo install --path .- Linux with FUSE 3 (
sudo apt install fuse3/sudo dnf install fuse3). - Rust 1.75+ to build from source.
- macOS support is planned (requires macFUSE); see Limitations.
timefs mount <repo> <mountpoint> [options]
timefs unmount <mountpoint>Common options:
| Flag | Effect |
|---|---|
-f, --foreground |
Run in the foreground (default while stabilizing; great for debugging). |
--allow-other |
Let other users access the mount (needs user_allow_other in /etc/fuse.conf). |
--submodules <mode> |
placeholder (default) or recurse. |
--lfs |
Serve real content for LFS objects present locally; otherwise show the pointer. |
--ref-snapshot |
Freeze branch/tag resolution at mount time instead of tracking moving refs. |
--cache-size <MB> |
Cap the in-memory object cache. |
--uid <id> / --gid <id> |
Override owner of every node. |
-v, -vv |
Increase log verbosity. |
/mnt/repo/
├── now/ # HEAD's tree — the repo as it is right now
├── at/ # resolve ANY revision on access
│ ├── HEAD~40/
│ ├── v1.2.0/
│ ├── main/
│ └── a1b2c3d.../
├── commits/<full-sha>/ # hash-addressed snapshots
├── refs/ # browsable, enumerable
│ ├── heads/
│ ├── tags/
│ └── remotes/
└── history/<path>/ # every version of one file across commits
refs/ is the browsable index you can ls and tab-complete. at/, commits/,
and history/ are resolvers: they accept an enormous set of names on access
without pre-listing them, which is what lets cd at/HEAD~40 just work.
timefs is a read-only FUSE
daemon in Rust. It reads Git objects directly through
gix (no subprocess per read), maps
Git trees to directories and blobs to files, and answers kernel filesystem
requests on demand:
- A
lookupforHEAD~40is fed straight to the revision parser. - A
readstreams blob bytes — large files stream rather than buffering. - Because a commit is an immutable snapshot, content-addressed nodes are cached
with very long kernel TTLs, while moving refs (
HEAD, branches) use short ones.
See docs/ARCHITECTURE.md and
docs/FUSE_MAPPING.md for the full design.
docs/ARCHITECTURE.md— module layout, backend boundary, and concurrency model.docs/FUSE_MAPPING.md— the authoritative namespace and metadata mapping.docs/LIMITATIONS.md— current constraints and intentionally unsupported cases.docs/DEMOS.md— terminal transcripts for mount, time-travel, diff, and history workflows.docs/timefs.1— the bundled man page source.
Honest and current:
- Read-only by design. Every mutation returns
EROFS. This is not a writable overlay and never will be. - Submodules default to a placeholder directory containing the pinned SHA; full recursion is opt-in and best-effort.
- Git LFS shows the pointer file unless the object is present locally and
--lfsis set. timefs never fetches over the network during a read. - Shallow clones can't show revisions they don't contain; those resolve to
ENOENTwith an explanatory log line. - Empty directories never appear — Git cannot represent them.
- macOS is planned, not shipped; it depends on macFUSE.
See docs/LIMITATIONS.md for the complete list.
cargo build
cargo test # unit + integration (needs fuse3 installed)
cargo test --test differential # verifies output byte-for-byte against `git`The correctness oracle is Git itself: differential tests assert that
cat $MNT/at/<rev>/<path> equals git show <rev>:<path> and that directory
listings match git ls-tree. Contributions welcome — please read
docs/ARCHITECTURE.md first.
Dual-licensed under either of
- MIT license (LICENSE-MIT)
- Apache License, Version 2.0 (LICENSE-APACHE)
at your option.