Which of your files exist in only one place?
Duplicate finders tell you what you have twice. lonecopy tells you the
opposite: the photos that live only on the external drive from 2019, the
documents that never made it to the NAS, the folder your backup job has
been silently skipping. It hashes every regular file in each place you
register, remembers the results, and reports every file whose content is
found in fewer places than you want.
The window above is python -m lonecopy gui; the same inventory is
available from the command line.
- Locations are directories (a laptop, a mounted drive, a NAS share) or
hash lists made by
sha256sum,shasum -a 256orrclone hashsum SHA256, so cloud remotes count without being mounted. - Copies are counted across locations. Two identical files on the same disk are one copy. A backup is a second place, not a second file.
- Rescans are incremental. A file whose size and mtime are unchanged keeps its stored hash; only new and changed files are read again.
- Safe against unplugged drives. A directory that comes back empty while its inventory is full is refused instead of wiped.
- Weak matches. Cloud services re-encode photos and editors rewrite metadata, so an exact hash can miss a copy that a human would accept. The report flags lone files that have a same-name (and same-size) file in another location, as a hint rather than a verdict.
Standard library only, Python 3.10+, one SQLite file. No pip install: clone and run as a module.
python -m lonecopy add laptop ~/Pictures
python -m lonecopy add usb-2019 "/Volumes/USB 2019"
rclone hashsum SHA256 gdrive:Photos > gdrive.sha256
python -m lonecopy add gdrive --hashfile gdrive.sha256
python -m lonecopy reportlonecopy: 1243 files with fewer than 2 copies (inventory ~/.local/share/lonecopy/inventory.db: 3 locations, 48210 files, 312.4 GB)
laptop (dir, /Users/me/Pictures)
1243 files only here, 8.7 GB [similar file elsewhere: 12 by name-size]
2019/Sommer 801 files 5.1 GB
2023/Handy 402 files 3.4 GB
by type: .jpg 1104, .heic 98, .mov 41
usb-2019 (dir, /Volumes/USB 2019)
0 files only here, 0 B
gdrive (hashfile, /Users/me/gdrive.sha256)
17 files only here, 0 B, 17 with unknown size
add scans right away. Later, after copying things around:
python -m lonecopy scan # all locations, only changed files are hashed
python -m lonecopy report --list # every affected file, tab separated
python -m lonecopy report --json # for scriptspython -m lonecopy guiThe same inventory, as a window: add folders and hash lists with a file dialog, rescan in the background with a progress line, browse the result as a tree of location and folder, and double-click a file to show it in Finder, Explorer or your Linux file manager. The report options (wanted copies, similar-by, empty files) sit in the toolbar; File > Export writes the JSON report or the tab separated file list.
The window uses tkinter, which is part of Python on Windows, on the
python.org builds for macOS, and on most Linux distributions. Where it is
missing: brew install python-tk@3.14 (Homebrew), apt install python3-tk
(Debian, Ubuntu), or re-run the Windows installer with tcl/tk enabled.
The command line works without it.
| Command | What it does |
|---|---|
add NAME PATH |
register a directory and scan it (--no-scan to only register) |
add NAME --hashfile FILE [--strip-prefix P] |
register a hash list; P is removed from the front of every path |
scan [NAME ...] |
update one, several or all locations; --ignore GLOB (repeatable), --workers N, --allow-empty |
report |
--min-copies N (default 2), --location NAME, --list, --json, --depth D, --top T, --weak name-size|name|none, --include-empty, --exit-code |
locations |
registered locations with file counts and last scan time |
gui |
open the desktop window |
remove NAME |
forget a location and its inventory |
The inventory lives at $LONECOPY_DB, else ~/.local/share/lonecopy/inventory.db;
--db PATH overrides both.
- Only regular files. Symlinks are never followed, sockets and devices are skipped.
- Junk that every copy carries is ignored by default:
.DS_Store,Thumbs.db,desktop.ini, AppleDouble._*files, Spotlight and Trash folders.--no-default-ignoreturns that off. - Zero-byte files are left out of the report unless
--include-emptyis given; their hash is identical everywhere and says nothing. - Hash lists carry no sizes. Their files count as copies, but their bytes show as unknown, and a weak match against them uses the name only.
- File names are compared NFC-normalised, so a name written by an old HFS+ volume matches the same name on APFS or ext4.
python -m lonecopy scan && python -m lonecopy report --exit-code >/dev/null || \
python -m lonecopy report --top 5 | mail -s "lonecopy: files with one copy" me@example.orguv venv .venv && uv pip install --python .venv/bin/python pytest
.venv/bin/python -m pytest -q- A second equality tier for re-encoded photos based on EXIF capture time and camera model, so a Google Photos copy of a JPEG counts as a copy.
- Reading
rclone lsjson --hashoutput directly, which carries sizes. - Hashing files inside archives.
lonecopy is developed and tested on a single laptop. If it saves you time, you can support the work:
MIT
