gitfix: put back what git still has, nothing more¶
The problem¶
A duplicate finder or disk cleaner runs across a folder of repos. Files
vanish; some "duplicates" become hardlinks. git checkout -- . brings the
files back but also wipes every uncommitted edit, duplicates files you
moved, and recreates folders you moved on purpose. And a hardlinked tracked
file changes whenever its twin in a backup folder is edited, looking like an
ordinary edit.
The method¶
Ask the content, not the names.
- Lost or moved: hash the repo's untracked files as git would. A gone file whose blob matches one of them was moved; leave it. Only files the same size as a lost blob are hashed. Empty files never count.
- Folder gone: look under the folders you name for one of the same name holding at least half its files with the same bytes. An old copy of the whole repo is not a new home.
- Hardlink damage: a modified tracked file with link count above 1 (and
changed after
--since) was edited through another name. - Restore: keep the changed bytes in
.git/gitfix/<time>/, thengit checkoutwrites a new file, breaking the link. Paths go to git literally, soodd*namerestores only itself.
Sample¶
want = {index[p] for p in deleted} - {EMPTY_BLOB}
sizes = set(blob_sizes(repo, want).values()) # git cat-file --batch-check
same = [p for p in untracked if os.lstat(p).st_size in sizes]
found = set(hashes(repo, same).values()) # git hash-object --stdin-paths
lost = [p for p in deleted if index[p] == EMPTY_BLOB or index[p] not in found]
linked = [p for p in modified
if os.lstat(p).st_nlink > 1 and os.lstat(p).st_mtime >= since]
Use it:
gitfix REPO... # report; exit 1 if damaged
gitfix --since 2026-10-18 --look /srv --fix REPO # restore
Limits¶
- A single file moved out of the repo is not searched for; it is restored (two copies, never zero).
- Files moved into ignored folders, or with clean filters (Git LFS), are restored rather than recognised as moved.
- Only what the index holds can come back.
Code¶
methods/gitfix/ in the methods repository: the script, a README and a
32-check test. Tried on a clone of a real repo with a file renamed, a folder
moved out, a file lost and a hardlinked edit: one restored, one unlinked with
its edit kept, the moves left alone.