Commit 692bffa
fix: a manual sync transfers what you selected, and asks first
Selecting two folders on a server and pressing Sync to Local copied the whole
directory. Two movies became forty thousand files.
The panes have always let you select entries. `page.tsx` built its request from
`source.path` and never read `state.selected`, so the selection reached the
counter in the pane footer and nothing else. Every transfer was the whole
directory, every time.
A second bug in the same click, found on the way. The rail sets the direction
and starts the transfer in one handler:
onDirection('rtl')
onRun()
`setDirection` does not change the value the current render closed over, so
`run` built its request from the PREVIOUS direction. Pressing the arrow that
was not armed transferred the opposite way: "Sync to Local" copied local over
the server. With Mirror armed it would have deleted the wrong side. The
direction is now passed to the run rather than read back out of state.
Selections now become an rsync `--files-from` list. The trap there, confirmed
against rsync 3.4.1 rather than assumed:
rsync -a --files-from=list src/ dst/ -> cd+++++++++ movieA/
rsync -a -r --files-from=list src/ dst/ -> cd+++++++++ movieA/
>f+++++++++ movieA/a.mkv
`--files-from` switches recursion off and `--archive` does not switch it back
on, so a selected folder would have copied as an empty folder and reported
success. `buildRsyncArgs` now forces `--recursive` whenever a file list is in
play. The list is NUL-separated (`--from0`), because a newline is legal in a
filename and a line-separated list would split one name into two paths that do
not exist.
Mirror plus a selection deletes only inside what was selected. Verified, not
assumed: the other reading of `--delete` here would empty the destination.
Everything previews and is approved now, mirror or not. Both the desktop and
the CLI skipped the dry run for a plain sync, on the reasoning that it "buys no
safety, because nothing is deleted either way". Deleting is not the only way to
regret a transfer, and this bug is the proof: by the time anything was on
screen it was already copying. The dialog says which it is, Sync or Mirror, and
carries the scope ("2 selected items" / "Whole folder") next to the counts,
because a count alone never says how many of what you asked for. Confirming
starts the request that was measured, not one rebuilt from whatever the panes
say by then.
All three interfaces:
- Desktop: the pane selection is sent, validated as entry names rather than
paths. That list becomes a `--files-from` rooted at the source directory, so
a renderer able to put `../` in it would be choosing which files leave the
machine.
- CLI: `--only NAME`, repeatable, and a plain sync now previews and asks. Only
where somebody is there to answer: a pipe, `--non-interactive` or `--yes`
proceeds as before, so scheduled profile runs keep working. `--no-preview`
restores the old skip-the-scan behaviour.
- TUI: it had a cursor and no way to select at all, and `s` started an
immediate transfer of everything on screen. Space marks entries, `s` scans
and asks before it moves anything.
Two smaller things this turned up:
- `--only` did not parse. It was missing from VALUE_FLAGS, so it consumed no
value, the names fell through to the positionals, and the flag silently did
nothing. Caught by running the built binary, not by reading it.
- The preview dialog's "Trust this pair from now on" checkbox is gone. It set
renderer state that nothing read, and could not have worked if wired:
saveProfile hard-codes `trustDeletes: false` on purpose, because unattended
mirroring is the one way a delete list runs with nobody looking at it.
Verified against the real rsync and the real dev server. `--only movieA --only
movieB` over ssh: 4 files, not 7. The desktop driven in headless Chromium under
the app's real CSP: the preview request carries the ssh source, both names, and
does not start until approved. The TUI driven in a pty: marks render, the
confirm appears, `n` transfers nothing, `y` copies the two marked folders with
their contents and leaves the rest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VScug5VRbcTuhiAoieeQ521 parent cd820b6 commit 692bffa
13 files changed
Lines changed: 839 additions & 102 deletions
File tree
- apps
- cli/src
- commands
- tui
- desktop
- electron
- main/services
- src
- app
- components
- packages
- rsync-core/src
- schemas/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
2 | 5 | | |
3 | 6 | | |
4 | 7 | | |
| |||
15 | 18 | | |
16 | 19 | | |
17 | 20 | | |
18 | | - | |
| 21 | + | |
19 | 22 | | |
20 | 23 | | |
21 | 24 | | |
| |||
61 | 64 | | |
62 | 65 | | |
63 | 66 | | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
64 | 100 | | |
65 | 101 | | |
66 | 102 | | |
| |||
100 | 136 | | |
101 | 137 | | |
102 | 138 | | |
103 | | - | |
104 | | - | |
105 | | - | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
106 | 153 | | |
107 | 154 | | |
108 | 155 | | |
| |||
147 | 194 | | |
148 | 195 | | |
149 | 196 | | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
150 | 219 | | |
151 | 220 | | |
152 | 221 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
| 37 | + | |
36 | 38 | | |
37 | 39 | | |
38 | 40 | | |
| |||
0 commit comments