Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion cli/src/mpftp/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2450,7 +2450,14 @@ def build_parser() -> argparse.ArgumentParser:
put = sub.add_parser("put", parents=[device_opts], help="Upload local file to board")
put.add_argument("local")
put.add_argument("remote")
put.add_argument("-r", "--recursive", action="store_true", help="Copy directories via fs_cp")
put.add_argument(
"-r",
"--recursive",
action="store_true",
help="Copy a folder. Like cp -r: if REMOTE is already a folder, LOCAL's folder "
"goes inside it (put -r lib /lib, with /lib there, writes /lib/lib); "
"otherwise REMOTE is created and gets LOCAL's contents",
)
put.add_argument(
"--verify",
action=argparse.BooleanOptionalAction,
Expand Down
9 changes: 8 additions & 1 deletion cli/src/mpftp/sidecar.py
Original file line number Diff line number Diff line change
Expand Up @@ -4142,7 +4142,14 @@ def stage(name: str, data: bytes) -> tuple[str, bytes]:
try:
t.fs_mkdir(root)
except Exception:
pass
# Already there is fine; a missing parent isn't, and would
# otherwise surface as a file inside it not being found.
if not self._remote_isdir(t, root):
parent = root.rstrip("/").rsplit("/", 1)[0] or "/"
raise RuntimeError(
f"can't create {root}: the folder {parent} isn't on the board "
f"(make it first: mpftp mkdir {parent})"
) from None
for dirpath, dirnames, filenames in os.walk(src):
# Prune ignored dirs in-place so os.walk does not descend into them.
dirnames[:] = [d for d in dirnames if not should_skip_transfer_entry(d)]
Expand Down
22 changes: 22 additions & 0 deletions cli/tests/test_put.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,25 @@ def test_nothing_changes_off_windows(self):

with mock.patch.object(sidecar.os, "name", "posix"):
self.assertIsNone(sidecar._windows_spelling("/mnt/c/Users/me/lib"))


class PutIntoMissingParentTests(unittest.TestCase):
"""mpftp#185: a missing parent folder is named, not a file inside it."""

def test_names_the_missing_folder(self):
import tempfile
from unittest import mock as _mock

from mpftp import sidecar

t = _mock.Mock()
t.fs_mkdir.side_effect = OSError(2, "ENOENT")
t.fs_stat.side_effect = OSError(2, "ENOENT")
s = sidecar.Session.__new__(sidecar.Session)
s._circuitpy_msc_root = lambda: None
with tempfile.TemporaryDirectory() as d:
(Path(d) / "a.py").write_text("v = 1\n")
with self.assertRaises(RuntimeError) as cm:
s._cp_local_to_remote(t, d, "/repro/cast", True, [], [])
self.assertIn("/repro isn't on the board", str(cm.exception))
t.fs_writefile.assert_not_called()
14 changes: 14 additions & 0 deletions docs/guides/transfer-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ mpftp df
`put`, `get` and `cp` verify with SHA-256 unless you pass `--no-verify`. Add
`-r` to `put` and `get` to copy a folder.

A folder copy works like `cp -r`. When the destination folder doesn't exist
yet, it's created and gets the folder's contents. When it does exist, the
folder goes inside it. So to update a library already on the board, put it
into the folder above:

```bash
mpftp put -r ./cast /cast # first time: creates /cast
mpftp put -r ./cast / # again: replaces the files in /cast
```

Putting `./cast` to `/cast` a second time writes `/cast/cast/...` and leaves
the old files where your code imports them from. The folder above the
destination has to exist: make it first with `mpftp mkdir`.

## Compile on upload

`--mpy` on `put` or `cp` (and the `mpftp.compileOnUpload` setting for the
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ and exits non-zero.
| Command | What it does |
|---|---|
| `ls`, `tree`, `df` | List, tree and free space |
| `put LOCAL REMOTE` | Upload. `-r` for a folder, `--no-verify` to skip SHA-256, `--mpy` to compile |
| `put LOCAL REMOTE` | Upload. `-r` for a folder (into REMOTE if that folder exists, [like `cp -r`](../guides/transfer-files.md)), `--no-verify` to skip SHA-256, `--mpy` to compile |
| `get REMOTE LOCAL` | Download, same options |
| `cp SRC DST` | Copy; `:` before a path marks the board side |
| `hash`, `mkdir`, `rm`, `touch`, `rename` | As named. `rm -r` removes a tree |
Expand Down
Loading