diff --git a/crates/openshell-cli/src/ssh.rs b/crates/openshell-cli/src/ssh.rs index 50bc2bbee9..5ef683a2b5 100644 --- a/crates/openshell-cli/src/ssh.rs +++ b/crates/openshell-cli/src/ssh.rs @@ -970,6 +970,28 @@ pub async fn sandbox_sync_up_files( if files.is_empty() { return Ok(()); } + + if files.len() == 1 + && local_upload_path_is_file_like(local_path) + && let Some(path) = dest + && !path.ends_with('/') + { + let (parent, target_name) = split_sandbox_path(path); + if parent != "/" { + return ssh_tar_upload( + server, + name, + Some(parent), + UploadSource::SinglePath { + local_path: base_dir.join(&files[0]), + tar_name: target_name.into(), + }, + tls, + ) + .await; + } + } + ssh_tar_upload( server, name, diff --git a/e2e/rust/tests/sync.rs b/e2e/rust/tests/sync.rs index a57793a74f..b799a58c8f 100644 --- a/e2e/rust/tests/sync.rs +++ b/e2e/rust/tests/sync.rs @@ -300,7 +300,8 @@ async fn upload_respects_gitignore_by_default() { } /// Verify that uploading a single tracked file from inside a git repo does not -/// expand to the entire repository. +/// expand to the entire repository, and that the destination basename is +/// respected (cp-style semantics). #[tokio::test] async fn upload_single_file_from_git_repo_only_uploads_that_file() { let mut guard = @@ -330,7 +331,7 @@ async fn upload_single_file_from_git_repo_only_uploads_that_file() { let local_path = repo.join("nested/config.txt"); let local_str = local_path.to_str().expect("local path is UTF-8"); guard - .upload_with_gitignore(local_str, "/sandbox/single-file", &repo) + .upload_with_gitignore(local_str, "/sandbox/renamed.txt", &repo) .await .expect("upload single tracked file with gitignore"); @@ -339,12 +340,16 @@ async fn upload_single_file_from_git_repo_only_uploads_that_file() { let download_str = download_dir.to_str().expect("download path is UTF-8"); guard - .download("/sandbox/single-file", download_str) + .download("/sandbox/renamed.txt", download_str) .await .expect("download uploaded single file"); - let uploaded = fs::read_to_string(download_dir.join("config.txt")).expect("read config.txt"); + let uploaded = fs::read_to_string(download_dir.join("renamed.txt")).expect("read renamed.txt"); assert_eq!(uploaded, "single-file-from-repo"); + assert!( + !download_dir.join("config.txt").exists(), + "original basename config.txt should not appear — file must be renamed to dest basename" + ); assert!( !download_dir.join("tracked.txt").exists(), "tracked.txt should not have been uploaded"