Skip to content

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>/, then git checkout writes a new file, breaking the link. Paths go to git literally, so odd*name restores 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.