Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

gitversions

A read-only FUSE filesystem that exposes the full commit history of every file in a git repository as a browsable directory tree.

Files in the mount are directly readable (showing their latest version), and each directory contains a .gitversions/ folder with the full commit history of every file — named with the commit hash and message — accessible to any tool that can read files: editors, diff utilities, file managers, grep, scripts. Convenience symlinks are created in your actual repo directories so you can cd .gitversions from anywhere.

The problem

Git stores rich version history, but accessing it requires specialized commands. To read an old version of a file, you need to know the commit hash and run git show. To compare two versions, you need git diff with exact revisions. To browse how a file evolved, you need git log and then manually extract each version.

This is fine for developers, but it creates friction in workflows where version history should be ambient — always accessible, never requiring you to context-switch into git's CLI.

Academic writing

In academic writing — drafting papers, maintaining research notes, revising after feedback — you frequently need to:

  • Reference earlier drafts: "What did I write in the version I submitted?" Open it directly.
  • Compare revisions: diff submitted-draft.md current.md with standard tools.
  • Search across history: grep a concept across all versions to find when you added or removed it.
  • Tag milestones: mark submissions, feedback rounds, revisions — and find them instantly.

gitversions makes all of this possible with regular filesystem operations, no git commands required.

How it works

repo/
├── .gitversions → .git/versions/.gitversions   ← symlink (auto-created)
├── drafts/
│   ├── .gitversions → ../.git/versions/drafts/.gitversions
│   └── paper.md
└── sources/
    ├── .gitversions → ../.git/versions/sources/.gitversions
    └── @Kant-1781-critique.md

.git/versions/                              ← mount point
├── _tags/
│   ├── submission/                         ← full repo tree at tag "submission"
│   │   └── paper.md                        ← the file as it was when you tagged it
│   └── after-feedback/
│       └── paper.md
├── sources/
│   ├── @Kant-1781-critique.md              ← readable file (latest version)
│   ├── deleted-file.md                     ← deleted files still accessible
│   └── .gitversions/                       ← version history for this directory
│       ├── @Kant-1781-critique.md/         ← version directory per file
│       │   ├── abc1234--initial-notes.md   ← version per commit
│       │   └── submission--ghi9012--final.md  ← tagged versions show tag name
│       └── authors/                        ← subdirs are symlinks for navigation
│           → ../authors/.gitversions
└── drafts/
    ├── paper.md                            ← readable file
    └── .gitversions/
        └── paper.md/
            ├── submission--abc1234--submit-v1.md
            ├── def5678--revisions-after-review.md
            └── ghi9012--first-draft.md

Nothing is stored on disk. All file content is served on-demand via git show. Directory listings are resolved lazily from git ls-tree and git log. Results are cached in memory with a 5-second TTL so new commits appear quickly.

Navigation

Each directory in your repo gets a .gitversions symlink pointing into the FUSE mount. From any directory, cd .gitversions gives you:

  • File version directories: each file becomes a folder of its versions
  • Subdirectory symlinks: navigate into child directories' histories seamlessly
  • working-copy.<ext>: symlink pointing to the actual file in your repo
  • HEAD-- prefix: the version matching the current HEAD is clearly marked

File metadata

  • Modified time (st_mtime): set to the commit timestamp, so ls -l shows when each version was committed
  • Extended attributes: every version file carries user.git.hash, user.git.subject, user.git.author, user.git.date, and user.git.tags — queryable via getfattr

Tags

Git tags are exposed in two ways:

  1. _tags/ directory: browse the complete repo tree at any tag. Access _tags/submission/path/to/file.md to read files as they were at that milestone.
  2. Version filename prefix: in per-file version listings, tagged commits show the tag name first: submission--abc1234--commit-message.md, making them immediately visible in any listing.

Tag names containing / are displayed with __ substitution (e.g., v1/release becomes v1__release).

Installation

Requires Linux with FUSE support (or macOS with macFUSE).

With uv (recommended)

The script is a self-contained uv script — uv handles the Python environment and fusepy dependency automatically.

# Install uv if you don't have it
curl -LsSf https://astral.sh/uv/install.sh | sh

# Copy the script to your PATH
cp gitversions ~/.local/bin/
chmod +x ~/.local/bin/gitversions

Manual

pip install fusepy
cp gitversions ~/.local/bin/
chmod +x ~/.local/bin/gitversions

Usage

Basic

# Mount (runs in background by default)
mkdir -p .git/versions
gitversions /path/to/repo /path/to/repo/.git/versions

# Mount in foreground (for debugging)
gitversions /path/to/repo /path/to/repo/.git/versions --foreground

# Mount without creating .gitversions symlinks
gitversions /path/to/repo /path/to/repo/.git/versions --no-symlink

# Unmount
fusermount -u /path/to/repo/.git/versions

On mount, .gitversions symlinks are automatically created in the repo root and every subdirectory. They are cleaned up on unmount/exit.

Browse versions

# From any directory in your repo, access version history
cd .gitversions
ls                              # files and subdirectory links
ls paper.md/                    # all versions of paper.md

# Navigate into subdirectories (symlinks chain through)
ls .gitversions/sources/        # follows symlink to sources' history

# Or access via the mount directly
ls .git/versions/path/to/file.md          # read latest version
ls .git/versions/.gitversions/file.md/    # list all versions

# Open a specific version in your editor
hx .gitversions/notes/idea.md/abc1234--first-draft.md

# Read an old version
cat .gitversions/paper.md/def5678--submitted-version.md

Compare versions

# Diff two versions with any diff tool
diff .gitversions/paper.md/abc1234--draft-1.md \
     .gitversions/paper.md/def5678--draft-2.md

# Side-by-side in vimdiff
vimdiff .gitversions/paper.md/abc1234--draft-1.md \
        .gitversions/paper.md/def5678--final.md

Search across history

# Find when a concept was added
grep -l "phenomenology" .gitversions/notes/idea.md/*

# Search all versions of all files in a directory
grep -r "Heidegger" .git/versions/sources/

Work with tags

# List all tags
ls .git/versions/_tags/

# Browse the repo at a tagged milestone
ls .git/versions/_tags/submission/

# Read a file as it was at a tag
cat .git/versions/_tags/after-feedback/paper.md

# Tagged versions are visible in per-file listings
ls .gitversions/paper.md/ | grep submission

Query metadata

# Show all extended attributes on a version
getfattr -d .gitversions/paper.md/abc1234--some-change.md

# Get just the commit message
getfattr -n user.git.subject .gitversions/paper.md/abc1234--some-change.md

# Get the commit date
getfattr -n user.git.date .gitversions/paper.md/abc1234--some-change.md

# Check file timestamps (shows commit dates)
ls -lt .gitversions/paper.md/

systemd user service

To keep the mount running automatically:

# ~/.config/systemd/user/gitversions.service
[Unit]
Description=Git Versions FUSE mount
After=local-fs.target

[Service]
Type=simple
ExecStartPre=-/usr/bin/fusermount -u %h/repo/.git/versions
ExecStartPre=/usr/bin/mkdir -p %h/repo/.git/versions
ExecStart=%h/.local/bin/gitversions %h/repo %h/repo/.git/versions --foreground
ExecStop=/usr/bin/fusermount -u %h/repo/.git/versions
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target

The fusermount -u in ExecStartPre (with - prefix to ignore failure) cleans up stale mounts from previous crashes.

systemctl --user daemon-reload
systemctl --user enable --now gitversions

Limitations

  • Read-only: writes return EROFS. This is by design — git history is immutable.
  • Main tree includes deleted files: the top-level directory structure shows all files that ever existed (including deleted ones). The .gitversions/ directories only show current HEAD files.
  • No Windows support: requires FUSE (Linux kernel module or macFUSE on macOS). Windows would need WinFsp with a different Python binding.
  • Single-threaded: uses nothreads=True for simplicity. Fine for interactive use on small-to-medium repos.
  • Symlinks require repo write access: .gitversions symlinks are created in the actual repo. Use --no-symlink if the repo is read-only.

How it compares

Existing git FUSE tools take a commit-centric approach — mounting one commit at a time (git-fs) or syncing a remote repo (gitfs). None of them provide a file-centric view where you can browse all versions of individual files.

gitversions is the inverse: organized around files, not commits. You navigate to a file and see its full history, rather than navigating to a commit and seeing its full tree.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages