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.
Release files for git-localhost-store 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| git_localhost_store-0.1.0.tar.gz | 57.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| git_localhost_store-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 73.3 kB
Release files / git_localhost_store-0.1.0.tar.gz
| Download URL | git_localhost_store-0.1.0.tar.gz |
|---|---|
| Size | 57.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0e84ce4ddc6b5695a9b1d316ea39a0c0cba36efef6b3bdddbf099240340aa7a0
|
|
BLAKE2b-256 checksum How to use checksums |
c6d26f778249deb8a47cb2c6b1b2074717c8a33dc8dfd662961a5ec883c78746
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / git_localhost_store-0.1.0-py3-none-any.whl
| Download URL | git_localhost_store-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
341e2b5654e688fb2bdf9477d3df0518605e294b1e66b56c3203af29c137d6ad
|
|
BLAKE2b-256 checksum How to use checksums |
2243ec8b51929b7576e4e449e0a2d49088538675a73e88497c7f1e2988c02945
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|