diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 03a799cbf..33f512dfe 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -57,9 +57,9 @@ impl Command for Cp { // .switch("force", "suppress error when no file", Some('f')) .switch("interactive", "ask user to confirm action", Some('i')) .switch( - "no-dereference", - "If the -r option is specified, no symbolic links are followed.", - Some('p'), + "no-symlink", + "no symbolic links are followed, only works if -r is active", + Some('n'), ) .category(Category::FileSystem) } @@ -218,7 +218,7 @@ impl Command for Cp { ) })?; - let not_follow_symlink = call.has_flag("no-dereference"); + let not_follow_symlink = call.has_flag("no-symlink"); let sources = sources.paths_applying_with(|(source_file, depth_level)| { let mut dest = destination.clone(); diff --git a/crates/nu-command/src/path/expand.rs b/crates/nu-command/src/path/expand.rs index 0760a4b87..0f11d0477 100644 --- a/crates/nu-command/src/path/expand.rs +++ b/crates/nu-command/src/path/expand.rs @@ -11,6 +11,7 @@ struct Arguments { strict: bool, columns: Option>, cwd: String, + not_follow_symlink: bool, } impl PathSubcommandArguments for Arguments { @@ -34,6 +35,7 @@ impl Command for SubCommand { "Throw an error if the path could not be expanded", Some('s'), ) + .switch("no-symlink", "Do not resolve symbolic links", Some('n')) .named( "columns", SyntaxShape::Table, @@ -58,6 +60,7 @@ impl Command for SubCommand { strict: call.has_flag("strict"), columns: call.get_flag(engine_state, stack, "columns")?, cwd: current_dir_str(engine_state, stack)?, + not_follow_symlink: call.has_flag("no-symlink"), }; input.map( @@ -84,6 +87,11 @@ impl Command for SubCommand { example: r"'foo\..\bar' | path expand", result: None, }, + Example { + description: "Expand an absolute path without following symlink", + example: r"'foo\..\bar' | path expand -n", + result: None, + }, ] } @@ -110,22 +118,35 @@ impl Command for SubCommand { } fn expand(path: &Path, span: Span, args: &Arguments) -> Value { - if let Ok(p) = canonicalize_with(path, &args.cwd) { - Value::string(p.to_string_lossy(), span) - } else if args.strict { - Value::Error { - error: ShellError::GenericError( - "Could not expand path".into(), - "could not be expanded (path might not exist, non-final \ - component is not a directory, or other cause)" - .into(), - Some(span), - None, - Vec::new(), - ), + if args.strict { + match canonicalize_with(path, &args.cwd) { + Ok(p) => { + if args.not_follow_symlink { + Value::string(expand_path_with(path, &args.cwd).to_string_lossy(), span) + } else { + Value::string(p.to_string_lossy(), span) + } + } + Err(_) => Value::Error { + error: ShellError::GenericError( + "Could not expand path".into(), + "could not be expanded (path might not exist, non-final \ + component is not a directory, or other cause)" + .into(), + Some(span), + None, + Vec::new(), + ), + }, } - } else { + } else if args.not_follow_symlink { Value::string(expand_path_with(path, &args.cwd).to_string_lossy(), span) + } else { + canonicalize_with(path, &args.cwd) + .map(|p| Value::string(p.to_string_lossy(), span)) + .unwrap_or_else(|_| { + Value::string(expand_path_with(path, &args.cwd).to_string_lossy(), span) + }) } } diff --git a/crates/nu-command/tests/commands/cp.rs b/crates/nu-command/tests/commands/cp.rs index d81a86236..f07593e9a 100644 --- a/crates/nu-command/tests/commands/cp.rs +++ b/crates/nu-command/tests/commands/cp.rs @@ -286,7 +286,7 @@ fn copy_dir_contains_symlink() { // make symbolic link and copy. nu!( cwd: sandbox.cwd(), - "rm tmp_dir/good_bye; cp -r -p tmp_dir tmp_dir_2", + "rm tmp_dir/good_bye; cp -r -n tmp_dir tmp_dir_2", ); // check hello_there exists inside `tmp_dir_2`, and `dangle_symlink` also exists inside `tmp_dir_2`. @@ -309,7 +309,7 @@ fn copy_dir_symlink_file_body_not_changed() { // make symbolic link and copy. nu!( cwd: sandbox.cwd(), - "rm tmp_dir/good_bye; cp -r -p tmp_dir tmp_dir_2; rm -r tmp_dir; cp -r -p tmp_dir_2 tmp_dir; echo hello_data | save tmp_dir/good_bye", + "rm tmp_dir/good_bye; cp -r -n tmp_dir tmp_dir_2; rm -r tmp_dir; cp -r -n tmp_dir_2 tmp_dir; echo hello_data | save tmp_dir/good_bye", ); // check dangle_symlink in tmp_dir is no longer dangling. diff --git a/crates/nu-command/tests/commands/path/expand.rs b/crates/nu-command/tests/commands/path/expand.rs index 3fd9d63df..d9168ac6c 100644 --- a/crates/nu-command/tests/commands/path/expand.rs +++ b/crates/nu-command/tests/commands/path/expand.rs @@ -24,6 +24,28 @@ fn expands_path_with_dot() { }) } +#[cfg(unix)] +#[test] +fn expands_path_without_follow_symlink() { + Playground::setup("path_expand_3", |dirs, sandbox| { + sandbox + .within("menu") + .with_files(vec![EmptyFile("spam.txt")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ln -s spam.txt menu/spam_link.ln; + echo "menu/./spam_link.ln" + | path expand -n + "# + )); + + let expected = dirs.test.join("menu").join("spam_link.ln"); + assert_eq!(PathBuf::from(actual.out), expected); + }) +} + #[test] fn expands_path_with_double_dot() { Playground::setup("path_expand_2", |dirs, sandbox| { @@ -75,4 +97,31 @@ mod windows { assert!(!PathBuf::from(actual.out).starts_with("~")); }) } + + #[test] + fn expands_path_without_follow_symlink() { + Playground::setup("path_expand_3", |dirs, sandbox| { + sandbox + .within("menu") + .with_files(vec![EmptyFile("spam.txt")]); + + let cwd = dirs.test(); + std::os::windows::fs::symlink_file( + cwd.join("menu").join("spam.txt"), + cwd.join("menu").join("spam_link.ln"), + ) + .unwrap(); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "menu/./spam_link.ln" + | path expand -n + "# + )); + + let expected = dirs.test.join("menu").join("spam_link.ln"); + assert_eq!(PathBuf::from(actual.out), expected); + }) + } }