opendotsync uses a 3-way merge model. Every push saves a parent snapshot (parent.tar.zst) that acts as the common ancestor for the next pull.
| Local vs ancestor | Remote vs ancestor | Result |
|---|---|---|
| unchanged | changed | accept remote |
| changed | unchanged | keep local |
| both changed the same | — | no-op |
| both changed differently | — | keep local, stash remote as conflict |
| local absent | remote present | write remote (new file) |
For .bash_history, .zsh_history, fish_history.
- Parse each file into
(timestamp, command)pairs. - 3-way merge: start with ancestor, add new local entries, add new remote entries not already present within a 60-second dedup window.
- Sort by timestamp ascending.
- Serialize back to the shell's native format.
Dedup window: entries with the same command text and timestamps within 60 seconds are considered duplicates — only one is kept.
Fallback: if either file can't be parsed, falls back to none merge.
Formats: bash (#<ts>\n<cmd>), zsh (: <ts>:<elapsed>;<cmd>), fish (- cmd: <cmd>\n when: <ts>).
For files like .gitignore, .aliases.
- Take all lines from local (in order).
- Append lines from remote not already present in local.
Fallback: binary files fall back to none merge.
When none detects that both sides changed relative to the ancestor, the remote version is stashed at:
~/.local/share/opendotsync/conflicts/v<remote_version>/<relative_path>
Local file is left untouched. Resolve with conflicts list/show/accept-local/accept-remote.
After every push or pull, the snapshot is saved locally as parent.tar.zst — the ancestor for the next 3-way merge.