git-localhost-store
Protect your local git repositories from accidental deletion.
Problem
A stray rm -rf can destroy weeks of work if you haven't pushed to a
remote. Local git repositories are fragile and ephemeral.
Solution
Every repository's objects live in a central store, indexed by working directory path. Delete the entire working directory and the commits are still there, recoverable by recreating the directory.
How It Works
When you git init or git clone (or git add in a fresh repo), hooks
run git-localhost-store, which:
- Moves
.git/into a per-repo store under~/.local/state/git-localhost-store/repos/<encoded-path>/. - Replaces
.gitwith a symbolic link to that store.
After the swap, .git is a symlink rather than a directory. Git follows
it transparently. Naive readers (lazy.nvim, ad-hoc shell scripts, IDE
plugins) that do open(repo + "/.git/HEAD") work unchanged, because the
symlink resolves through the filesystem.
Path encoding
claude-code-slug names the store: every character that is not ASCII
alphanumeric becomes exactly one -.
/home/you/projects/myrepo → -home-you-projects-myrepo
It is a dependency rather than a copy because the name has to survive the tool that wrote it: a store is named by this function, so a second implementation that disagrees anywhere doesn't fail, it strands a repository in a directory nobody looks in.
Installation
uv tool install git-localhost-store
git-localhost-store-install
The second command is the one that touches your system, and it is
idempotent -- re-run it after upgrading, or after moving the venv. It
creates the store root, writes the hook template, points
init.templateDir at it, and aims one stable path
(~/.local/share/git-localhost-store/bin/git-localhost-store) at the
freshly installed relocator.
git allows exactly one init.templateDir. If yours is already set to
something else, the installer says so and changes nothing; merge the
hooks by hand.
Existing repositories keep their old hooks and are unaffected -- hooks
are copied at init time, not read from the template afterwards. To
convert one, run git-localhost-store in it.
Recovery
After rm -rf of a working directory:
mkdir ~/projects/myrepo && cd ~/projects/myrepo
git init
git-localhost-store # adopts the surviving store, restores tracked files
Re-cloning instead: if the workdir had a remote, a plain git clone
into the original path works directly -- the hook force-syncs
remote-tracking refs and fast-forwards the local branch when it can. It
refuses only when the store holds local commits that are not a
fast-forward of the fresh clone. That refusal wants a human, not another
rm -rf.
Commands
git-localhost-store [HOOK-NAME]
Convert the repository containing the current directory, or recover it
if its store already exists. Idempotent, and a no-op when .git is
already a symlink, or is a gitfile (worktree or submodule). Any other
shape is an error: it asserts and exits non-zero rather than guess.
Hooks pass their own name as the argument; you don't.
git-localhost-store-install
See Installation.
Layout
~/.local/share/git-localhost-store/ # written by the installer
├── bin/git-localhost-store # -> the installed console script
└── template-repo/hooks/ # git init template
├── shared # the hook body
└── post-{index-change,commit,checkout} # -> shared
~/.local/state/git-localhost-store/ # your repositories -- back this up
└── repos/<encoded-path>/ # one gitdir per workdir
Limitations
- Protects commits and staged changes, not unstaged modifications.
- Recovery requires recreating the directory at the exact original path.
- Stores in the state directory can still be deleted by hand.
- Not a substitute for a remote.
- One worktree per store.
git worktree addfrom the central gitdir still works; it is just not automated here.
Design Principles
- Automatic -- works without user intervention.
- Transparent to naive readers --
.git/HEADresolves through the symlink as an ordinary file. - Deterministic -- the recovery path follows from the workdir path.
- Explicit errors -- failures are visible. No
|| true. - Idempotent -- hooks and
git-localhost-storeare safe to re-run.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file git_localhost_store-0.1.0.tar.gz.
File metadata
- Download URL: git_localhost_store-0.1.0.tar.gz
- Upload date:
- Size: 57.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e84ce4ddc6b5695a9b1d316ea39a0c0cba36efef6b3bdddbf099240340aa7a0
|
|
| MD5 |
d7545afdca4b0cd28d93829dd6cfe80b
|
|
| BLAKE2b-256 |
c6d26f778249deb8a47cb2c6b1b2074717c8a33dc8dfd662961a5ec883c78746
|
File details
Details for the file git_localhost_store-0.1.0-py3-none-any.whl.
File metadata
- Download URL: git_localhost_store-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
341e2b5654e688fb2bdf9477d3df0518605e294b1e66b56c3203af29c137d6ad
|
|
| MD5 |
ea442b7e3712d596a75fe717c8e40a4f
|
|
| BLAKE2b-256 |
2243ec8b51929b7576e4e449e0a2d49088538675a73e88497c7f1e2988c02945
|