coliru

A minimal, flexible, dotfile installer
git clone https://git.ashermorgan.net/coliru/
Log | Files | Refs | README

commit ad0a2a96304563eccc0e8274d5d6da8aeba6b91f
parent 952f62ae824a50845a0b527f449f391a67b5da0c
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Mon,  1 Jul 2024 18:20:24 -0700

Refactor send_dir to merge src and dst directories

Diffstat:
Msrc/ssh.rs | 143++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
1 file changed, 95 insertions(+), 48 deletions(-)

diff --git a/src/ssh.rs b/src/ssh.rs @@ -1,4 +1,5 @@ use shellexpand::tilde_with_context; +use std::fs::read_dir; use std::path::{MAIN_SEPARATOR_STR, PathBuf}; use std::process::Command; use super::local::copy_file; @@ -42,22 +43,29 @@ fn stage_file(src: &str, dst: &str, staging_dir: &str) -> Result<(), String> { .map_err(|why| why.to_string()) } -/// Recursively copy a directory to another machine via SCP +/// Copy a directory to another machine via SCP and merge it with a destination +/// directory #[allow(dead_code)] fn send_dir(src: &str, dst: &str, host: &str) -> Result<(), String> { - let mut cmd = Command::new("scp"); - if cfg!(test) { - // SSH options and port for test server hard coded for now - cmd.args(["-o", "StrictHostKeyChecking=no", "-P", "2222"]); - } - cmd.args(["-r", src, &format!("{host}:{dst}")]); - - let status = cmd.status().map_err(|why| why.to_string())?; - if status.success() { - Ok(()) - } else { - Err(format!("SCP exited with {status}")) + // To avoid the source directory being copied as a subdirectory of the + // destination directory, we must send the contents of the directory + // item by item. + for item in read_dir(&src).map_err(|why| why.to_string())? { + let _src = item.map_err(|why| why.to_string())?.path(); + + let mut cmd = Command::new("scp"); + if cfg!(test) { + // SSH options and port for test server hard coded for now + cmd.args(["-o", "StrictHostKeyChecking=no", "-P", "2222"]); + } + cmd.args(["-r", &_src.to_string_lossy(), &format!("{host}:{dst}")]); + + let status = cmd.status().map_err(|why| why.to_string())?; + if !status.success() { + return Err(format!("SCP exited with {status}")); + } } + Ok(()) } #[cfg(test)] @@ -70,41 +78,6 @@ mod tests { use std::fs; #[test] - #[cfg(target_family = "unix")] - fn test_send_dir_basic() { - let tmp = setup_integration("test_send_dir_basic"); - - write_file(&tmp.local.join("foo"), "contents of foo"); - write_file(&tmp.local.join("bar"), "contents of bar"); - - let result = send_dir(&tmp.local.to_str().unwrap(), "~/", SSH_HOST); - - assert_eq!(result, Ok(())); - let contents1 = read_file(&tmp.ssh.join("foo")); - assert_eq!(contents1, "contents of foo"); - let contents2 = read_file(&tmp.ssh.join("bar")); - assert_eq!(contents2, "contents of bar"); - } - - #[test] - #[cfg(target_family = "unix")] - fn test_send_dir_nested() { - let tmp = setup_integration("test_send_dir_nested"); - - write_file(&tmp.local.join("foo"), "contents of foo"); - fs::create_dir_all(&tmp.local.join("dir")).unwrap(); - write_file(&tmp.local.join("dir").join("bar"), "contents of bar"); - - let result = send_dir(tmp.local.to_str().unwrap(), "~/", SSH_HOST); - - assert_eq!(result, Ok(())); - let contents1 = read_file(&tmp.ssh.join("foo")); - assert_eq!(contents1, "contents of foo"); - let contents2 = read_file(&tmp.ssh.join("dir").join("bar")); - assert_eq!(contents2, "contents of bar"); - } - - #[test] fn test_stage_file_tilde() { let tmp = setup_integration("test_stage_file_tilde"); @@ -157,4 +130,78 @@ mod tests { assert_eq!(dst_real.exists(), true); assert_eq!(read_file(&dst_real), "contents of foo"); } + + #[test] + #[cfg(target_family = "unix")] + fn test_send_dir_basic() { + let tmp = setup_integration("test_send_dir_basic"); + + write_file(&tmp.local.join("foo"), "contents of foo"); + write_file(&tmp.local.join("bar"), "contents of bar"); + + let dst = "~/test_send_dir_basic"; + let dst_foo = tmp.ssh.join("foo"); + let dst_bar = tmp.ssh.join("bar"); + + let result = send_dir(tmp.local.to_str().unwrap(), dst, SSH_HOST); + + assert_eq!(result, Ok(())); + assert_eq!(dst_foo.exists(), true); + assert_eq!(read_file(&dst_foo), "contents of foo"); + assert_eq!(dst_bar.exists(), true); + assert_eq!(read_file(&dst_bar), "contents of bar"); + } + + #[test] + #[cfg(target_family = "unix")] + fn test_send_dir_nested_dir() { + let tmp = setup_integration("test_send_dir_nested_dir"); + + let src_foo = tmp.local.join("foo"); + let src_bar = tmp.local.join("dir").join("bar"); + write_file(&src_foo, "contents of foo"); + fs::create_dir_all(&src_bar.parent().unwrap()).unwrap(); + write_file(&src_bar, "contents of bar"); + + let dst = "~/test_send_dir_nested_dir"; + let dst_foo = tmp.ssh.join("foo"); + let dst_bar = tmp.ssh.join("dir").join("bar"); + + let result = send_dir(tmp.local.to_str().unwrap(), dst, SSH_HOST); + + assert_eq!(result, Ok(())); + assert_eq!(dst_foo.exists(), true); + assert_eq!(read_file(&dst_foo), "contents of foo"); + assert_eq!(dst_bar.exists(), true); + assert_eq!(read_file(&dst_bar), "contents of bar"); + } + + #[test] + #[cfg(target_family = "unix")] + fn test_send_dir_merge_dir() { + let tmp = setup_integration("test_send_dir_merge_dir"); + + let src_bar = tmp.local.join("dir").join("bar"); + fs::create_dir_all(src_bar.parent().unwrap()).unwrap(); + write_file(&src_bar, "new contents of bar"); + + let dst = "~/test_send_dir_merge_dir"; + let dst_foo = tmp.ssh.join("foo"); + let dst_bar = tmp.ssh.join("dir").join("bar"); + let dst_baz = tmp.ssh.join("dir").join("baz"); + write_file(&dst_foo, "old contents of foo"); + fs::create_dir_all(&dst_bar.parent().unwrap()).unwrap(); + write_file(&dst_bar, "old contents of bar"); + write_file(&dst_baz, "old contents of baz"); + + let result = send_dir(tmp.local.to_str().unwrap(), dst, SSH_HOST); + + assert_eq!(result, Ok(())); + assert_eq!(dst_foo.exists(), true); + assert_eq!(read_file(&dst_foo), "old contents of foo"); + assert_eq!(dst_bar.exists(), true); + assert_eq!(read_file(&dst_bar), "new contents of bar"); + assert_eq!(dst_baz.exists(), true); + assert_eq!(read_file(&dst_baz), "old contents of baz"); + } }