Skip to content

Safety guarantees

dotsync changes files in your home directory without asking, so it has to be trustworthy. These are the promises it makes, and how it keeps each one.

Every local file dotsync replaces or deletes is first copied to ~/.local/state/dotsync/backups/<run>/<path>. If the copy fails, the file is left alone. A backup is never overwritten, not even by a retried sync: a second copy of the same file in the same run gets a numbered suffix. See Restore a previous version.

Concurrent edits are never merged or discarded

Section titled “Concurrent edits are never merged or discarded”

If a file changed on two machines, it becomes a conflict. Both versions stay exactly as they are until you resolve it. dotsync never picks a winner, and never attempts a textual merge.

If one machine deletes a file that another changed, the changed version survives everywhere. And a missing single-file entry, or a managed directory that’s missing or empty, is restored rather than deleted everywhere, so an accidental rm -rf or an unmounted disk can’t wipe your configuration on every machine.

Edits made during a sync are never clobbered

Section titled “Edits made during a sync are never clobbered”

dotsync decides based on what it read, then reads each file again immediately before replacing or deleting it. If you saved the file in between, that file is skipped and handled on the next sync. A file that changes while it’s being read is detected too.

  • Targets must resolve inside $HOME, and outside dotsync’s own directories. Absolute paths in the manifest are rejected.
  • Inside a managed directory, dotsync never reads or writes through a parent directory that has been replaced by a symlink, so it can’t be tricked into writing outside the managed tree.
  • Apart from dotsync’s own files, the background agent’s definition and git’s configuration, nothing outside managed entries is read or written.
  • When an entry stops being managed, whether removed from the manifest, excluded on a machine or skipped for another OS, its files are left in place, never deleted.

A file is written to a temporary file in the same directory, flushed to disk, and renamed into place, so programs never see a half-written file. An entry can opt into "write": "inplace" for programs that need the same inode kept; that write is still flushed to disk before dotsync moves on.

  • The cache clone is marked dirty before dotsync changes it, and reset before it’s used again, so an interrupted sync leaves nothing behind.
  • A file’s base only moves after a successful local write or a successful push.
  • The state file is written atomically. If it’s ever unreadable anyway, it’s set aside, and every file that differs from the repository becomes a conflict rather than being replaced.

If a sync is killed at any point, the next one finishes the job.

The repository has a single, linear history: dotsync never merges or rebases, and a machine that loses a push race redoes its work on top of the winner’s commit. Every machine applies the same rules to the same manifest, so machines that have synced hold identical managed files. The only differences are the ones dotsync reports: conflicts, blocked files, per-OS entries and per-machine exclusions.

  • Every synced version of every file is in the repository’s history, with the machine that made it.
  • Every local replacement or deletion has a backup, and the most recent backup of each file is kept however old it is.

Scenario tests simulate several machines, each with its own home directory, sharing one repository. They cover concurrent edits, offline machines, push races, symlink tricks, lost state, emptied directories, existing files on new machines, backups, secrets and encrypted files. They run on macOS and on Linux for every change, alongside fuzz tests for path and pattern handling.