Synchronization
How a sync finds what changed on each side, decides what each file needs, and runs only what you confirm.
The memory of the last sync
For every file it synchronizes, sync-servers records both sides as they were at that moment: the size and modification time of the local copy and of the server copy. These sync records are kept per site and server in the state folder of this computer.
On the next sync each side is compared with its own record, never with the other side's clock. A local file whose size or time moved is changed locally; a server file whose size or time moved is changed on the server. When both moved, the file changed on both sides, and it waits for your decision instead of being overwritten. A local file is held to its record exactly, since the disk reports the same time for a file nobody touched. On the server, times closer than the tolerance, 2 seconds by default, count as equal, because many servers round them in listings.
A file with no record yet, as on a first sync, is compared across the two sides: the newer copy wins, and copies of the same age that differ ask for a decision. In checksum mode (sync.compare set to checksum) SHA-256 hashes settle what times cannot.
sync-servers status shows what changed on each side without planning anything. mark-synced records files as in sync without moving them, and state reset forgets the records so the next sync starts from scratch.
From a change to an action
Each direction turns what changed into an action. A file whose row says "not listed" is counted as skipped and never moves.
| What happened since the last sync | Put newer files | Get newer files | Both |
|---|---|---|---|
| Changed here | Put | not listed | Put |
| Changed on the server | not listed | Get | Get |
| Changed on both sides | Resolve | Resolve | Resolve |
| New here | Put | not listed | Put |
| New on the server | not listed | Get | Get |
| Deleted on the server, unchanged here | Put again | not listed | Put again |
| Deleted here, unchanged on the server | not listed | Get again | Get again |
| Changed here, deleted on the server | Resolve | Resolve | Resolve |
| Deleted here, changed on the server | Resolve | Resolve | Resolve |
| No record, newer here | Put | not listed | Put |
| No record, newer on the server | not listed | Get | Get |
| No record, differs, same age | Resolve | Resolve | Resolve |
A Resolve row is unchecked and cannot run until you choose Put, Get, Skip or Synchronized for it.
The delete option
With --delete, or the delete checkbox, a file missing on the other side is offered for deletion instead of being copied back. The checkbox is worded for the direction: Delete remote files not on local drive when putting, Delete local files not on remote server when getting.
| What happened | Put newer files | Get newer files | Both |
|---|---|---|---|
| Deleted here since the last sync | Delete on the server, checked | Get again | Delete on the server, checked |
| Deleted on the server since the last sync | Put again | Delete here, checked | Put again |
| On the server only, never synchronized | Delete on the server, unchecked | Get | Delete on the server, unchecked |
| Here only, never synchronized | Put | Delete here, unchecked | Put |
A file that was never synchronized is only ever offered for deletion unchecked: nothing is deleted just because it has no record.
Put and Get of single files
sync-servers put css/site.css and get move the files and folders you name at once, through the same checks. When one of them changed on the other side, or is held back by the guard, the preview opens first so you can decide.
The preview
The preview lists only files with a difference; unchanged files are counted, never listed. Its summary line counts what will happen, such as Files: 37 will be updated, 169 will be skipped, 12 need a decision. Each row has a checkbox, an action and a status that says why, in a sentence:
| Row | Why |
|---|---|
| Resolve | Changed on both sides, or differs with no record. Choose an action first. |
| Guarded | Matches a sensitive pattern such as .env; see the guard. |
| Never-synchronized delete | Offered by the delete option for a file with no record. |
| Problem | A name that cannot be used here: not valid on this system, a symbolic link that is not followed, or two names that differ only by case on a file system that cannot tell them apart. Problem rows never run. |
You can change any row: check or uncheck it, or choose another action that the file can take. Put needs a local copy, Get a server copy, and Synchronized both.
Running a plan
- Checked once more. Just before a file moves, both sides are looked at again. A file that changed since the preview is skipped and reported as stale.
- Written whole or not at all. A file is written to a partial copy, its size checked, and only then renamed into place, so an interrupted transfer never leaves half a file.
- Local files kept. A local file that a run deletes or overwrites is moved to the recovery folder of the state folder first, and kept 14 days.
- Through links. When the site follows symbolic links, a Get through a link writes the file the link points to and keeps the link, and rows that reach one file through two names run one at a time.
- Folders in order. Folders are created before their files and removed after them.
- Retries. A dropped connection or a timeout is retried on a fresh connection after 1, 2 and 4 seconds.
- Recorded as it goes. Records are saved during the run, so a cancelled or interrupted sync keeps what it finished. Cancelling starts no new transfer and removes partial files.
Safety guards
Some plans stop before anything moves, with exit code 5:
- Mass delete. Deleting more than 50 files, or more than a quarter of a side's files, asks you to type the count. A script needs
--allow-mass-delete. Both limits are settings of the site. - Empty root. When one side is empty and the plan would delete everything on the other, the run is refused: an unmounted drive or a wrong root directory looks exactly like that.
- Top of a server. Deletes are refused when the server's root directory is
/or a drive root, unless the server allows it withallowRootDeletes.