@@ -421,19 +421,44 @@ def _get_sftp(self, timeout):
421
421
422
422
@classmethod
423
423
def _push_file (cls , sftp , src , dst ):
424
- sftp .put (src , dst )
424
+ try :
425
+ sftp .put (src , dst )
426
+ # Maybe the dst was a folder
427
+ except OSError :
428
+ # This might fail if the folder already exists
429
+ with contextlib .suppress (IOError ):
430
+ sftp .mkdir (dst )
431
+
432
+ new_dst = os .path .join (
433
+ dst ,
434
+ os .path .basename (src ),
435
+ )
436
+
437
+ return cls ._push_file (sftp , src , new_dst )
438
+
425
439
426
440
@classmethod
427
441
def _push_folder (cls , sftp , src , dst ):
442
+ # Behave like the "mv" command or adb push: a new folder is created
443
+ # inside the destination folder, rather than merging the trees.
444
+ dst = os .path .join (
445
+ dst ,
446
+ os .path .basename (src ),
447
+ )
448
+ return cls ._push_folder_internal (sftp , src , dst )
449
+
450
+ @classmethod
451
+ def _push_folder_internal (cls , sftp , src , dst ):
428
452
# This might fail if the folder already exists
429
453
with contextlib .suppress (IOError ):
430
454
sftp .mkdir (dst )
455
+
431
456
for entry in os .scandir (src ):
432
457
name = entry .name
433
458
src_path = os .path .join (src , name )
434
459
dst_path = os .path .join (dst , name )
435
460
if entry .is_dir ():
436
- push = cls ._push_folder
461
+ push = cls ._push_folder_internal
437
462
else :
438
463
push = cls ._push_file
439
464
@@ -446,8 +471,16 @@ def _push_path(cls, sftp, src, dst):
446
471
447
472
@classmethod
448
473
def _pull_file (cls , sftp , src , dst ):
474
+ # Pulling a file into a folder will use the source basename
475
+ if os .path .isdir (dst ):
476
+ dst = os .path .join (
477
+ dst ,
478
+ os .path .basename (src ),
479
+ )
480
+
449
481
with contextlib .suppress (FileNotFoundError ):
450
482
os .remove (dst )
483
+
451
484
sftp .get (src , dst )
452
485
453
486
@classmethod
0 commit comments