feat: sync only the files and folders you picked - #20
Merged
Conversation
Both surfaces could sync a directory. Neither could sync three things out of
it, which is the one thing an SFTP client makes trivial.
Desktop: tick entries in the source pane and the transfer button changes from
"Sync to web-01" to "Send 2 -> web-01". Only those entries move. Nothing
ticked keeps the old meaning, the whole directory. The panes already tracked a
selection; it just had nothing to do with transfers.
CLI: `--only NAME`, repeatable.
diskpush ./site/ prod:/var/www/ --only index.html --only assets
diskpush mirror ./cache/ prod:/srv/cache/ --only sessions
The names go to rsync as a NUL-separated `--files-from` list rather than as
extra source arguments. Both work; the list is not bounded by the command-line
length limit, so four hundred files is the same shape as one, and with
`--from0` it can express every name a filesystem allows — including the ones
containing a newline, which no line-based list can.
Two behaviours of `--files-from`, both established against rsync 3.4.1 rather
than read off the flag summary, and both of which would have shipped as bugs:
- It turns recursion OFF, and `--archive` does not turn it back on. A
selected folder arrives as an EMPTY DIRECTORY. `buildRsyncArgs` now
restates `--recursive` whenever a files-from list is in play.
- `--delete` stays scoped to the listed entries. `mirror --only cache`
removes destination files inside `cache/` and leaves the rest of the
destination alone — the behaviour you would want, and worth stating
because it is not the one you would fear.
A selection is a choice among what the source directory holds, so a name is
the only thing it can be: `writeSelectionList` refuses `..`, absolute paths
and NUL bytes, and the IPC contract types the field as entry names, so a
compromised renderer cannot turn a selection into a path.
Also moves `EntryNameSchema` above its first use. `TransferRequestSchema` now
references it, and a `const` is not hoisted — declaring it afterwards is a
temporal-dead-zone ReferenceError at import time, which typechecks perfectly
and kills the main process on launch. Caught by loading the built module, not
by reading it.
Verified against real files end to end: one file; a folder, which must arrive
with its contents; several at once including a name with spaces; `--dry-run`
changing nothing; `..` and absolute paths refused; no temp list left behind;
and `mirror --only` leaving an unrelated destination file untouched. The
desktop change was screenshotted — two rows ticked, "2 selected" in the pane
footer, "SEND 2 web-01" on the armed button and "SYNC TO Local" on the other,
which is right because the count belongs to the source pane.
504 tests, up from 491.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UeSWg1Czsb2Lwxj8vHUnA4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both surfaces could sync a directory. Neither could sync three things out of it — the one thing an SFTP client makes trivial.
Desktop: tick entries in the source pane and the transfer button changes from Sync to web-01 to Send 2 → web-01. Only those move. Nothing ticked keeps the old meaning: the whole directory. The panes already tracked a selection; it just had nothing to do with transfers.
CLI:
--only NAME, repeatable.Mechanism
Names go to rsync as a NUL-separated
--files-fromlist rather than as extra source arguments. Both work; the list is not bounded by the command-line length limit, so four hundred files is the same shape as one, and with--from0it can express every name a filesystem allows — including ones containing a newline, which no line-based list can.Two behaviours of
--files-from, both established against rsync 3.4.1 rather than read off the flag summary, and both of which would otherwise have shipped as bugs:--archivedoes not turn it back on. A selected folder arrives as an empty directory.buildRsyncArgsnow restates--recursivewhenever a files-from list is in play.--deletestays scoped to the listed entries.mirror --only cacheremoves destination files insidecache/and leaves the rest of the destination alone — worth stating because it is not the behaviour you would fear.Safety
A selection is a choice among what the source directory holds, so a name is the only thing it can be.
writeSelectionListrefuses.., absolute paths and NUL bytes, and the IPC contract types the field as entry names, so a compromised renderer cannot turn a selection into a path.Also moves
EntryNameSchemaabove its first use.TransferRequestSchemanow references it, and aconstis not hoisted — declaring it afterwards is a temporal-dead-zone ReferenceError at import time, which typechecks perfectly and kills the main process on launch. Caught by loading the built module, not by reading it.Verified against real files
One file; a folder (must arrive with contents); several at once including a name with spaces;
--dry-runchanging nothing;..and absolute paths refused; no temp list left behind; andmirror --onlyleaving an unrelated destination file untouched.The desktop change was screenshotted: two rows ticked,
2 selected · 24.6 KBin the pane footer, SEND 2 → web-01 on the armed button and SYNC TO Local on the other — right, because the count belongs to the source pane.504 tests, up from 491. Typecheck and
pnpm smoke:desktopgreen.🤖 Generated with Claude Code
https://claude.ai/code/session_01UeSWg1Czsb2Lwxj8vHUnA4