<div align="center">

# timefs

**Mount a Git repository as a filesystem and browse its entire history like normal directories.**

[![CI](https://img.shields.io/github/actions/workflow/status/itsbryanman/timefs/ci.yml?branch=main&label=CI&logo=github)](https://github.com/itsbryanman/timefs/actions)
[![crates.io](https://img.shields.io/crates/v/timefs?logo=rust)](https://crates.io/crates/timefs)
[![docs.rs](https://img.shields.io/docsrs/timefs?logo=docsdotrs)](https://docs.rs/timefs)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](#license)
[![MSRV](https://img.shields.io/badge/MSRV-1.75-orange?logo=rust)](https://www.rust-lang.org)
[![Platform](https://img.shields.io/badge/platform-Linux-informational?logo=linux&logoColor=white)](#requirements)
[![Downloads](https://img.shields.io/crates/d/timefs?logo=rust)](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 &

$ 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/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:

| 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. |

## 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.
