Syncing across machines ======================= .. Page contents are rendered by furo's right-hand sidebar; an explicit .. `.. contents::` directive collides with it (furo TOC JS error), so it is omitted. protonfs is designed so several machines can sync the same directory tree to the same Proton Drive location. This page describes that workflow and the pitfalls worth knowing. The sync contract ----------------- A protonfs-managed directory keeps its state in ``.protonfs/``: - ``config.json`` — the **remote root** and defaults. Shared: commit it so every clone syncs to the same place. - ``ignore`` — which files are in scope (gitignore syntax). Shared: commit it so every clone syncs the same set. - ``index.json`` — this machine's record of what it has uploaded (local hashes, mtimes, remote paths). **Local only** — gitignore it; it is rebuilt per device and would only cause churn and false conflicts if shared. ``protonfs setup`` writes this split for you: a ``.protonfs/.gitignore`` that ignores ``index.json`` (and the transient ``refresh-state.json``) while keeping ``config.json`` and ``ignore`` tracked, plus a ``.protonfs/.gitattributes`` that exempts these small control files from git-LFS — so a clone without an LFS pull gets the real config rather than pointer stubs. Both are written idempotently and preserve any lines you add yourself. Setting up a **subdirectory** of a larger git repo is safe: ``setup`` runs the repo-wide git-LFS migration only when the protonfs root is the git toplevel. In a subdirectory it skips migration (and leaves any git-LFS pointer files there untouched); pass ``--migrate-lfs`` to force it or ``--no-migrate-lfs`` to always skip. ``setup`` also creates the configured ``remote_root`` on Drive if it does not exist yet (it must live under ``/my-files``), so the first ``push`` works without hand-creating folders. First-time setup on a client ---------------------------- .. code-block:: bash cd git pull # get the shared contract (.protonfs/config.json + ignore) protonfs doctor # verify the proton-drive binary + OS keyring (add --fix if headless) protonfs auth login # only if not already authenticated protonfs refresh # learn what is already on Drive -> seeds THIS machine's index ``refresh`` is the key cross-client primitive: metadata-only, no download, it makes a fresh machine aware of everything already on Drive so it will not re-upload what another client already sent. Uploading data -------------- .. code-block:: bash protonfs status # what is local-only vs locally-indexed (index only) protonfs status --remote # the same, checked against Drive protonfs push # a subtree protonfs push # everything in scope that is new or changed .. important:: A bare ``push`` is the normal form. A file that changed locally since this machine last pushed it is sent as a **new revision** of the same Drive file (Drive keeps the earlier revision in its version history), provided the remote copy is still the one this machine recorded. A file that changed on *both* sides is reported as a conflict instead, and only then is ``--resolve`` needed. ``--resolve local`` (``replace``) moves the existing remote file to the trash and uploads a new one in its place, rather than adding a revision. Use it for a genuine conflict you want settled in favour of the local copy, not as a default. Verifying an upload ------------------- ``proton-drive`` can report a file as transferred when it never landed, so push re-lists every directory it uploads into and only records a file once the remote copy's plaintext size matches the local one. A file that fails that check is reported (``under-delivered``, or ``unverified`` when the listing carries no size at all), left unrecorded, and retried by the next ``push``. The exit code is ``1`` whenever any file was not verified; with ``-v``, compare the planned count on the ``uploading files=N`` line with ``transferred=`` on the summary line. Removing files -------------- .. code-block:: bash protonfs rm # trash the remote copy (reversible) protonfs rm -f # trash, then permanently delete ``rm -f`` has one **permanent limitation**. proton-drive addresses a trashed node for permanent deletion by its path under ``/trash`` (``/trash/``), and offers no working way to target a specific trashed node by its stable UID. So when two or more trashed items share a basename, protonfs cannot safely tell which one is yours and **refuses to permanently delete** — it leaves the item trashed (still reversible) and tells you so. To resolve a duplicate-basename case, empty that specific item from trash via the Proton Drive app or web UI, or just leave it trashed (trash is reversible, so nothing is lost). This is an upstream constraint, not a protonfs choice; a live probe test flags it automatically if a future proton-drive lifts it. Keeping clients in sync ----------------------- The standard loop on any client: .. code-block:: bash git pull # shared contract protonfs refresh # reconcile local index with Drive # (--prune drops entries for files deleted on Drive) protonfs status # local-only / locally-indexed / conflict / ... protonfs push # send local-only and locally-changed files up protonfs pull --refresh # bring remote-only down (if this machine wants them) Resolving a divergence on pull ------------------------------ A file that was edited **locally** *and* changed on the **remote** since the last sync is a divergence. A bare ``pull`` never touches such a file — it leaves it in place, reports it, and exits non-zero — so a local edit is never silently overwritten. Choose a side explicitly with ``--resolve``: .. code-block:: bash protonfs pull --resolve remote # overwrite the local copy with the remote one protonfs pull --resolve local # keep local (it stays queued for the next push) protonfs pull --resolve both # fetch the remote copy as .remote to merge ``--resolve=both`` writes the remote version alongside your file under a ``.remote`` suffix (untracked) so you can diff and merge by hand, then delete the suffixed copy. Files that changed only on the remote (your local copy is still in sync) are brought down normally by ``pull --resolve ``; no local edit is at risk there. ``status`` also sets an **exit code** so an unattended caller can branch without parsing the printed counts: - ``0`` — clean: every file is ``locally-indexed``, ``metadata-only`` (deliberately not materialised here) or an LFS pointer stub (nothing to reconcile). - ``1`` — drift: non-conflict divergence exists (something to push, pull, or prune). - ``2`` — conflict: at least one file changed on both sides (``both-modified``, only with ``--remote``), or changed locally with no remote view to say whether the remote moved too (``conflict``). A bare ``push`` uploads a ``conflict`` file as a new revision when the remote still holds the copy this machine recorded, and reports a real conflict otherwise; ``status --remote`` tells you which first. Conflict outranks drift when both are present, e.g.: .. code-block:: bash protonfs status; case $? in 0) echo "matches the index" ;; 1) echo "drift -- run push/pull" ;; 2) echo "conflict -- resolve first" ;; esac