<div align="center">
# timefs
**Mount a Git repository as a filesystem and browse its entire history like normal directories.**
[](https://github.com/itsbryanman/timefs/actions)
[](https://crates.io/crates/timefs)
[](https://docs.rs/timefs)
[](#license)
[](https://www.rust-lang.org)
[](#requirements)
[](https://crates.io/crates/timefs)
*`cd` into a commit. `cat` a file as it existed then. `diff -r` two commits with plain Unix tools.*
</div>
---
```console
$ timefs mount ~/code/linux /mnt/linux &
VERSION = 6
PATCHLEVEL = 1
$ cd /mnt/linux/at/HEAD~40
# A commit diff, using nothing but diff:
$ diff -r /mnt/linux/at/HEAD~1 /mnt/linux/at/HEAD
```
Because 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.
## Why
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/B` is 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 `cd` into it.
## Install
```console
# From crates.io
cargo install timefs
# From source
git clone https://github.com/itsbryanman/timefs
cd timefs
cargo install --path .
```
### Requirements
- 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](#limitations).
## Usage
```console
timefs mount <repo> <mountpoint> [options]
timefs unmount <mountpoint>
```
Common options:
| `-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. |
## Filesystem layout
```
/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.
## How it works
timefs is a read-only [FUSE](https://www.kernel.org/doc/html/latest/filesystems/fuse.html)
daemon in Rust. It reads Git objects directly through
[`gix`](https://github.com/GitoxideLabs/gitoxide) (no subprocess per read), maps
Git trees to directories and blobs to files, and answers kernel filesystem
requests on demand:
- A `lookup` for `HEAD~40` is fed straight to the revision parser.
- A `read` streams 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`](docs/ARCHITECTURE.md) and
[`docs/FUSE_MAPPING.md`](docs/FUSE_MAPPING.md) for the full design.
## Docs
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — module layout, backend boundary, and concurrency model.
- [`docs/FUSE_MAPPING.md`](docs/FUSE_MAPPING.md) — the authoritative namespace and metadata mapping.
- [`docs/LIMITATIONS.md`](docs/LIMITATIONS.md) — current constraints and intentionally unsupported cases.
- [`docs/DEMOS.md`](docs/DEMOS.md) — terminal transcripts for mount, time-travel, diff, and history workflows.
- [`docs/timefs.1`](docs/timefs.1) — the bundled man page source.
## Limitations
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
`--lfs` is set. timefs never fetches over the network during a read.
- **Shallow clones** can't show revisions they don't contain; those resolve to
`ENOENT` with 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`](docs/LIMITATIONS.md) for the complete list.
## Development
```console
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`](docs/ARCHITECTURE.md) first.
## License
Dual-licensed under either of
- MIT license ([LICENSE-MIT](LICENSE-MIT))
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
at your option.